diff --git a/packaging/macos/Info.plist b/packaging/macos/Info.plist
index 7ceecbb..550a831 100644
--- a/packaging/macos/Info.plist
+++ b/packaging/macos/Info.plist
@@ -36,9 +36,9 @@
CFBundlePackageType
APPL
CFBundleShortVersionString
- 3.2.0
+ 3.6.0
CFBundleVersion
- 1
+ 3.6.0
LSMinimumSystemVersion
13.0
NSHumanReadableCopyright
diff --git a/packaging/macos/build_app.sh b/packaging/macos/build_app.sh
index a3e8231..66d47b3 100755
--- a/packaging/macos/build_app.sh
+++ b/packaging/macos/build_app.sh
@@ -78,6 +78,11 @@ APP_VERSION="$(tr -d '[:space:]' < "$REPO/VERSION" 2>/dev/null || true)"
if [ -n "$APP_VERSION" ] && command -v plutil >/dev/null 2>&1; then
plutil -replace CFBundleShortVersionString -string "$APP_VERSION" "$CONTENTS/Info.plist"
echo "==> bundle CFBundleShortVersionString = $APP_VERSION (from VERSION)"
+ # Sparkle decides "is this newer?" from CFBundleVersion (the appcast's sparkle:version),
+ # not the short string. It stayed "1" on every build, so no installed copy was ever
+ # offered an update. Use the same version.
+ plutil -replace CFBundleVersion -string "$APP_VERSION" "$CONTENTS/Info.plist"
+ echo "==> bundle CFBundleVersion = $APP_VERSION (from VERSION)"
fi
printf 'APPL????' > "$CONTENTS/PkgInfo"
diff --git a/tests/test_app_bundle.py b/tests/test_app_bundle.py
index bc39b78..90ec337 100644
--- a/tests/test_app_bundle.py
+++ b/tests/test_app_bundle.py
@@ -75,3 +75,20 @@ def test_build_assembles_bundle_and_selftest_passes():
capture_output=True, text=True, timeout=60,
)
assert st.returncode == 0, f"--selftest failed: {st.stderr}\n{st.stdout}"
+
+
+def test_the_bundle_carries_the_release_version_as_its_build_number():
+ # Sparkle offers an update only when the appcast's sparkle:version (CFBundleVersion) is
+ # higher than the installed one. It stayed "1" on every build, so no installed copy was
+ # ever offered an update. Both version keys now come from VERSION.
+ import plistlib
+ version = (REPO / "VERSION").read_text(encoding="utf-8").strip()
+ static = plistlib.loads((PKG / "Info.plist").read_bytes())
+ assert static["CFBundleVersion"] == static["CFBundleShortVersionString"] == version, "static plist matches VERSION"
+ if sys.platform != "darwin":
+ return # the build itself needs macOS (Swift/AppKit launcher, plutil)
+ with tempfile.TemporaryDirectory() as td:
+ r = subprocess.run(["bash", str(BUILD), "--out", td, "--clean"], capture_output=True, text=True, timeout=120)
+ assert r.returncode == 0, r.stderr
+ built = plistlib.loads((Path(td) / "Sovereign AI Workstation.app" / "Contents" / "Info.plist").read_bytes())
+ assert built["CFBundleVersion"] == built["CFBundleShortVersionString"] == version