Skip to content

Restore MSI upgrades from beta releases (#656) - #660

Draft
leynos wants to merge 1 commit into
mainfrom
issue-656-restore-msi-upgrades-from-beta-releases-to-later-releases
Draft

Restore MSI upgrades from beta releases (#656)#660
leynos wants to merge 1 commit into
mainfrom
issue-656-restore-msi-upgrades-from-beta-releases-to-later-releases

Conversation

@leynos

@leynos leynos commented Sep 2, 2026

Copy link
Copy Markdown
Owner

Summary

This branch restores Windows MSI replacement for successive beta releases and
for a final release replacing beta installations in the same release line.
The WiX major-upgrade contract allows same-numeric-version upgrades while
retaining the stable upgrade family and blocking numeric downgrades.

Closes #656.

Review walkthrough

Validation

  • make check-fmt: passed
  • make lint: passed
  • make doc-coverage: passed (99.13%)
  • make test: passed (2,774 nextest tests and doctests)
  • make markdownlint: passed
  • make nixie: passed
  • coderabbit review --agent: completed with zero findings

Notes

Functional msiexec upgrade testing remains deferred: package construction
runs in the external shared action, and this repository has no MSI test
harness. The parsed-WiX and workflow-input contracts cover the authored
upgrade metadata and its stable-family wiring.

References

Summary by Sourcery

Restore same-version Windows MSI upgrades across beta and final releases while retaining downgrade protection.

Bug Fixes:

  • Restore Windows MSI replacement for successive beta releases and for final releases replacing beta installations within the same numeric release line.

Enhancements:

  • Preserve the stable MSI upgrade family while preventing numeric-version downgrades and enabling safe same-version replacement.
  • Add automated validation for WiX upgrade metadata, workflow package inputs, and the documented MSI replacement behavior.

Documentation:

  • Update the user guide to document beta-to-beta and beta-to-final MSI replacement within a version series.

Tests:

  • Add contract tests covering WiX major-upgrade metadata, shared workflow upgrade-family wiring, and installation documentation.

@coderabbitai

coderabbitai Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true

Warning

Your free Security trial is over. An organization admin can upgrade to Advanced for continuous pull request security review or dismiss this notice.


Comment @coderabbitai help to get the list of available commands.

@sourcery-ai

sourcery-ai Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Reviewer's Guide

Restores Windows MSI replacement across successive beta releases and beta-to-final releases by configuring WiX same-version major upgrades, preserving stable workflow wiring, and adding parsed-WiX, workflow, and documentation contract tests.

Flow diagram for Windows MSI beta-to-final replacement

flowchart LR
    Beta1[Beta MSI 0.1.0-beta1] --> Install[Install MSI]
    Beta2[Beta MSI 0.1.0-beta2] --> Upgrade[WiX major upgrade]
    Final[Final MSI 0.1.0] --> Upgrade
    Upgrade --> Replace[Replace existing installation]
    Upgrade --> StableFamily[Stable UpgradeCode family]
Loading

File-Level Changes

Change Details Files
Configure WiX major upgrades to replace beta and final MSI packages within the same numeric release while preserving downgrade protection.
  • Retain the stable upgrade family identifier and allow WiX to generate a fresh product code.
  • Enable same-version replacement, schedule removal after install initialization, and keep numeric downgrade errors enabled.
installer/Package.wxs
CHANGELOG.md
Add source-level tests that validate the authored WiX upgrade metadata without requiring MSI construction.
  • Parse Package.wxs and assert the stable UpgradeCode, generated ProductCode behavior, major-upgrade settings, schedule, and downgrade message.
  • Add the roxmltree development dependency.
tests/installer_package_wxs_tests.rs
Cargo.toml
Cargo.lock
Preserve and verify the shared Windows packaging inputs required for a stable MSI upgrade family.
  • Assert the package action continues to use the binary name for product and install-directory names and the workflow version input.
tests/workflow_build_and_package.rs
Document that later beta and final releases replace earlier MSI installations in the same numeric version series.
  • Update the user guide wording and add a documentation contract test for the replacement claim.
docs/users-guide.md
tests/documentation_installation_tests.rs

Assessment against linked issues

Issue Objective Addressed Explanation
#656 Add WiX major-upgrade metadata that enables beta-to-beta and beta-to-final MSI replacement while retaining the stable upgrade family and preventing numeric downgrades.
#656 Ensure the Windows packaging workflow and shared packaging action preserve the stable upgrade-family wiring and pass the required product name, installation directory, and version inputs.
#656 Validate the installer upgrade contract and keep the user documentation aligned with the supported beta and final release replacement behavior.

Possibly linked issues


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

codescene-access[bot]

This comment was marked as outdated.

Configure the WiX package for major upgrades that replace earlier beta
packages and promote beta installations to the corresponding final release.
Keep the upgrade family stable while allowing WiX to issue a fresh ProductCode
for every package, and lock the contract with XML, workflow, and documentation
tests.
@leynos
leynos force-pushed the issue-656-restore-msi-upgrades-from-beta-releases-to-later-releases branch from 73d0f95 to 4316c18 Compare September 5, 2026 21:02
codescene-access[bot]

This comment was marked as outdated.

@codescene-access codescene-access Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No quality gates enabled for this code.

@leynos

leynos commented Sep 5, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai Please investigate the cause of the following issue using codegraph exploration and research, identify a fix and provide an AI coding agent prompt for the fix:

Run make SHELL=bash test
  make SHELL=bash test
  shell: C:\Program Files\Git\bin\bash.EXE --noprofile --norc -e -o pipefail {0}
  env:
    WINDOWS_LANE_VCPUS: 4
    NEXTEST_VERSION: 0.9.133
    MDTABLEFIX_VERSION: 0.5.1
    PYTHON_BASELINE: 3.14
    CARGO_TERM_COLOR: always
    BUILD_PROFILE: debug
    BUILD_JOBS: -j 4
    NEXTEST_BUILD_JOBS: --build-jobs 4
    NEXTEST_TEST_JOBS: -j 4
    RUSTC_WRAPPER: sccache
    SCCACHE_DIR: D:\a\netsuke\netsuke/.sccache
    SCCACHE_CACHE_SIZE: 4G
    NETSUKE_RUST_TOOLCHAIN: nightly-2026-08-23
    CARGO_INCREMENTAL: 0
    CARGO_PROFILE_DEV_DEBUG: 0
    RUST_BACKTRACE: short
    RUSTFLAGS: -D warnings
    CARGO_UNSTABLE_SPARSE_REGISTRY: true
    CARGO_REGISTRIES_CRATES_IO_PROTOCOL: sparse
    UV_TOOL_BIN_DIR: D:\a\_temp\uv-tool-bin-dir
    UV_TOOL_DIR: D:\a\_temp\uv-tool-dir
    UV_PYTHON_INSTALL_DIR: D:\a\_temp\uv-python-dir
    UV_PYTHON: 3.14
...
        FAIL [   0.111s] (1766/2506) netsuke-build::documentation_installation_tests installation_examples_match_source_and_release_contracts
  stdout ───

    running 1 test
    test installation_examples_match_source_and_release_contracts ... FAILED

    failures:

    failures:
        installation_examples_match_source_and_release_contracts

    test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 1 filtered out; finished in 0.07s
    
  stderr ───
    Error: users' guide should document MSI replacement within a version series

    Stack backtrace:
       0: std::backtrace_rs::backtrace::win64::trace
                 at /rustc/c54751567b19c4ceb08b0412d83529c2568cba8b/library\std\src\..\..\backtrace\src\backtrace\win64.rs:85
       1: std::backtrace_rs::backtrace::trace_unsynchronized
                 at /rustc/c54751567b19c4ceb08b0412d83529c2568cba8b/library\std\src\..\..\backtrace\src\backtrace\mod.rs:66
       2: std::backtrace::Backtrace::create
                 at /rustc/c54751567b19c4ceb08b0412d83529c2568cba8b/library\std\src\backtrace.rs:331
       3: std::backtrace::Backtrace::capture
                 at /rustc/c54751567b19c4ceb08b0412d83529c2568cba8b/library\std\src\backtrace.rs:296
       4: <anyhow::Error>::msg::<&str>
       5: documentation_installation_tests::main
       6: std::rt::lang_start::<()>
       7: documentation_installation_tests::main
       8: std::rt::lang_start::<()>
       9: documentation_installation_tests::main
      10: core::ops::function::FnOnce::call_once
                 at /rustc/c54751567b19c4ceb08b0412d83529c2568cba8b/library\core\src\ops\function.rs:250
      11: test::__rust_begin_short_backtrace<enum2$<core::result::Result<tuple$<>,alloc::string::String> >,enum2$<core::result::Result<tuple$<>,alloc::string::String> > (*)()>
                 at /rustc/c54751567b19c4ceb08b0412d83529c2568cba8b/library\test\src\lib.rs:733
      12: test::run_test_in_process
                 at /rustc/c54751567b19c4ceb08b0412d83529c2568cba8b/library\test\src\lib.rs:756
      13: test::run_test::closure$0
                 at /rustc/c54751567b19c4ceb08b0412d83529c2568cba8b/library\test\src\lib.rs:677
      14: test::run_test::closure$1
                 at /rustc/c54751567b19c4ceb08b0412d83529c2568cba8b/library\test\src\lib.rs:707
      15: std::sys::backtrace::__rust_begin_short_backtrace<test::run_test::closure_env$1,tuple$<> >
                 at /rustc/c54751567b19c4ceb08b0412d83529c2568cba8b/library\std\src\sys\backtrace.rs:166
      16: std::thread::lifecycle::spawn_unchecked::closure$1::closure$0
                 at /rustc/c54751567b19c4ceb08b0412d83529c2568cba8b/library\std\src\thread\lifecycle.rs:70
      17: core::panic::unwind_safe::impl$25::call_once
                 at /rustc/c54751567b19c4ceb08b0412d83529c2568cba8b/library\core\src\panic\unwind_safe.rs:275
      18: std::panicking::catch_unwind::do_call
                 at /rustc/c54751567b19c4ceb08b0412d83529c2568cba8b/library\std\src\panicking.rs:574
      19: std::panicking::catch_unwind
                 at /rustc/c54751567b19c4ceb08b0412d83529c2568cba8b/library\std\src\panicking.rs:542
      20: std::panic::catch_unwind
                 at /rustc/c54751567b19c4ceb08b0412d83529c2568cba8b/library\std\src\panic.rs:359
      21: std::thread::lifecycle::spawn_unchecked::closure$1
                 at /rustc/c54751567b19c4ceb08b0412d83529c2568cba8b/library\std\src\thread\lifecycle.rs:68
      22: core::ops::function::FnOnce::call_once<std::thread::lifecycle::spawn_unchecked::closure_env$1<test::run_test::closure_env$1,tuple$<> >,tuple$<> >
                 at /rustc/c54751567b19c4ceb08b0412d83529c2568cba8b/library\core\src\ops\function.rs:250
      23: std::sys::thread::windows::impl$0::new::thread_start
                 at /rustc/c54751567b19c4ceb08b0412d83529c2568cba8b/library\std\src\sys\thread\windows.rs:58
      24: BaseThreadInitThunk
      25: RtlUserThreadStart

  Cancelling due to test failure: 3 tests still running
        PASS [   0.110s] (1767/2506) netsuke-build::env_path_property_tests composition_prepends_and_preserves_order
        PASS [   8.509s] (1768/2506) netsuke-build::dependabot_config_tests manifest_discovery::generated_layouts_include_only_tracked_manifests
        PASS [  59.413s] (1769/2506) netsuke-build::command_env_ui_tests cli_configuration_fixture_compiles
────────────
     Summary [ 112.087s] 1769/2506 tests run: 1768 passed, 1 failed, 2 skipped
        FAIL [   0.111s] (1766/2506) netsuke-build::documentation_installation_tests installation_examples_match_source_and_release_contracts

https://github.com/leynos/netsuke/actions/runs/33991862694/job/101375401545?pr=660

@coderabbitai

coderabbitai Bot commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '=== repository status and PR diff summary ==='
git status --short
git diff --stat origin/main...HEAD
git diff --name-status origin/main...HEAD

printf '%s\n' '=== target test outline ==='
ast-grep outline tests/documentation_installation_tests.rs --items all || true

printf '%s\n' '=== assertion and MSI-documentation references ==='
rg -n -C 8 \
  "users' guide should document MSI replacement within a version series|MSI replacement|within a version series|beta-to-beta|beta-to-final" \
  tests installer .github README.md docs 2>/dev/null || true

printf '%s\n' '=== relevant test source ==='
nl -ba tests/documentation_installation_tests.rs | sed -n '1,260p'

printf '%s\n' '=== changed documentation and test patches ==='
git diff --find-renames origin/main...HEAD -- \
  tests/documentation_installation_tests.rs \
  ':(glob)**/*.md' \
  installer/Package.wxs \
  .github

Length of output: 1348


🏁 Script executed:

#!/bin/bash
set -u

printf '%s\n' '=== checkout and refs ==='
git rev-parse --show-toplevel
git rev-parse HEAD
git branch -a || true
git remote -v || true

printf '%s\n' '=== candidate files ==='
fd -t f . tests installer .github docs 2>/dev/null | \
  rg '(documentation_installation_tests\.rs|Package\.wxs|install|guide|user.*guide|\.md$)' || true

printf '%s\n' '=== test outline and source ==='
ast-grep outline tests/documentation_installation_tests.rs --items all 2>&1 || true
nl -ba tests/documentation_installation_tests.rs | sed -n '1,300p' 2>&1 || true

printf '%s\n' '=== exact assertion and nearby documentation matches ==='
rg -n -i -C 10 \
  "users' guide should document MSI replacement within a version series|MSI replacement|within a version series|beta.*replace|replace.*beta|Windows.*MSI" \
  . -g '!target/**' 2>&1 || true

printf '%s\n' '=== PR `#660` file list and refs ==='
gh pr view 660 --repo leynos/netsuke --json number,headRefName,baseRefName,url,files 2>&1 || true

Length of output: 31509


🏁 Script executed:

#!/bin/bash
set -u

printf '%s\n' '=== direct base-to-head patch ==='
git diff --find-renames origin/main..HEAD -- \
  docs/users-guide.md \
  tests/documentation_installation_tests.rs \
  installer/Package.wxs \
  tests/installer_package_wxs_tests.rs \
  tests/workflow_build_and_package.rs 2>&1 || true

printf '%s\n' '=== source bytes and literal-match diagnosis ==='
python3 - <<'PY'
from pathlib import Path
guide = Path("docs/users-guide.md").read_bytes()
test = Path("tests/documentation_installation_tests.rs").read_text()
needle = "installing a later beta or final MSI\nreplaces the existing installation"
print("guide_has_lf:", needle.encode() in guide)
print("guide_has_crlf:", needle.replace("\n", "\r\n").encode() in guide)
print("guide_line_endings:", "CRLF" if b"\r\n" in guide else "LF-only")
for number, line in enumerate(guide.decode().splitlines(), 1):
    if 65 <= number <= 75:
        print(f"{number}: {line}")
PY

printf '%s\n' '=== line-ending controls ==='
fd -HI -t f '(\.gitattributes|\.editorconfig|\.gitconfig)' . -x sh -c 'echo --- "$1"; cat "$1"' sh {} 2>&1 || true
git config --get core.autocrlf 2>&1 || true

printf '%s\n' '=== WiX major-upgrade contract ==='
sed -n '1,180p' installer/Package.wxs 2>&1 || true
sed -n '1,220p' tests/installer_package_wxs_tests.rs 2>&1 || true

Length of output: 12298


🤖 web_search executed:

WiX Toolset v4 MajorUpgrade AllowSameVersionUpgrades Schedule afterInstallInitialize official documentation

Length of output: 14207


@leynos

Fix tests/documentation_installation_tests.rs. The failure is caused by a line-ending-sensitive assertion.

docs/users-guide.md contains the required wording on the PR branch:

installing a later beta or final MSI
replaces the existing installation

The test requires that exact string with \n:

users_guide.contains(concat!(
    "installing a later beta or final MSI\n",
    "replaces the existing installation"
))

The Windows checkout converts the Markdown file to CRLF because the repository has no .gitattributes rule that preserves LF. The document then contains MSI\r\nreplaces, so the LF-only needle does not match. The test passes against the LF file in the repository, which confirms that the wording itself is present.

Do not change installer/Package.wxs for this failure. The WiX configuration matches the intended upgrade contract. AllowSameVersionUpgrades="yes" enables same-numeric-version major upgrades. Schedule="afterInstallInitialize" allows Windows Installer to roll back removal of the prior product if the upgrade fails. FireGiant documentation

Use this coding-agent prompt:

Fix the Windows-specific failure in `tests/documentation_installation_tests.rs`.

Cause:
- `assert_release_installation_contract()` asserts one LF-delimited literal:
  `installing a later beta or final MSI\nreplaces the existing installation`.
- On Windows, Git checkout can convert `docs/users-guide.md` to CRLF.
- `String::contains()` does not match the LF-only needle against CRLF text.
- The repository does not force LF line endings for Markdown files.

Change the MSI replacement documentation contract test so that it is independent of line endings and Markdown wrapping.

Requirements:
1. Keep the assertion that `docs/users-guide.md` documents both facts:
   - `installing a later beta or final MSI`
   - `replaces the existing installation`
2. Do not require the two facts to be separated by a literal `\n`.
3. Preserve the existing error message:
   `users' guide should document MSI replacement within a version series`
4. Do not modify `docs/users-guide.md`, `installer/Package.wxs`, or the WiX upgrade settings for this fix.
5. Keep the test focused on the documentation contract and compatible with `RUSTFLAGS=-D warnings`.

Preferred implementation:
- Define an array containing the two required fragments.
- Use `.into_iter().all(|fragment| users_guide.contains(fragment))` in the existing `ensure!` assertion.

After the edit, run:
- `make SHELL=bash test`
- the targeted `documentation_installation_tests` test if the repository provides a direct command for it.

🐇

You are interacting with an AI system.

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.

Restore MSI upgrades from beta releases to later releases

1 participant