Release facts authority:
.github/release-manifest.json(schemadocs/schemas/release-manifest.schema.json). Published:v1.4.5(version1.4.5, build13),arm64only, minimum macOS15.0. Runtime identities: apptech.reidar.vifty, daemontech.reidar.vifty.daemon, helpertech.reidar.vifty.helper, CLItech.reidar.vifty.ctl. Canonical artifact:Vifty-v1.4.5.zipwith checksum assetVifty-v1.4.5.zip.sha256and SHA-25613fa763cbfdca3e77fcf6f657df6d51b32e19a4d25dd17a79614635fe844b0d5. Public artifact trust:passed/developer-id-notarizedfor TeamIDX88J3853S2; source174dcd28a343de7f797d682d02c0f70e26b72c2e, CI run31283125895, Release run31284620552. Tag policy:v1.4.5remains recorded assigned-verifiedevidence; signed tags are mandatory from version1.3.3onward. Separate exact-build claims: installed release reviewpending; manual Fixed/Curve/Auto compatibilitypending.
Auto-update is not enabled for v1.4.5; its exact public binary contains the advisory release-availability checker but no executable downloader or in-place installer. The first public release that contains the current update-checking code must therefore be installed manually. Source-first, unsigned-dev, local ad-hoc, CI, and other ineligible builds do not make update requests.
Current source implements an advisory release-availability checker for future exact Vifty Developer ID builds. It does not download executable code, replace Vifty.app, run an installer, or silently change the privileged helper. A separate in-place updater has a higher trust bar and has not been implemented.
- Only an exact Developer ID signed Vifty build with bundle identifier
tech.reidar.viftyand TeamIDX88J3853S2is eligible to check. Source-first, unsigned-dev, local ad-hoc, CI, debug fixture, and other ineligible builds make zero update requests. - Eligible builds check the fixed
https://api.github.com/repos/Reedtrullz/Vifty/releases/latestendpoint. Automatic checking is enabled by default, can be turned off in Settings, and runs no more than once per 24 hours; Check now remains available as an explicit action. The scheduler combines its persisted wall-clock attempt with an in-process monotonic deadline so clock changes cannot shorten that automatic interval. - A response is accepted only when it is a non-draft, non-prerelease stable
vMAJOR.MINOR.PATCHrelease with exactly four uploaded, nonempty canonical assets:Vifty-v<version>.zip,Vifty-v<version>.zip.sha256,Vifty-v<version>-artifact-summary.json, andVifty-v<version>-release-checklist.md. - Vifty does not trust release or asset links supplied by the API. When newer availability metadata matches, Update to latest version opens the locally constructed
https://github.com/Reedtrullz/Vifty/releases/tag/v<version>page in the user's browser. - The checker never downloads the archive or other release assets and never installs or replaces executable code. Users retain the documented release-verification and app-replacement path.
- Keep the canonical
Vifty-v<version>.zipname reserved for a Developer ID signed and notarized release artifact, and keep Homebrew checksum handoff tied to that verified artifact rather than an unsigned-dev zip.
This is availability metadata, not artifact-trust proof. The checker does not fetch or validate the checksum body, artifact-summary contents, release checklist, signed tag, archive bytes, Developer ID signature, notarization ticket, stapling, or Gatekeeper result.
An eligible build's automatic or manual check sends an ordinary HTTPS request to GitHub. GitHub receives normal request metadata such as the Mac's public IP address, request timing, and Vifty's version-bearing User-Agent. Vifty sends no account, fan, sensor, power, Codex, profile, or analytics payload.
The request uses an ephemeral session with no persistent cookie, URL cache, or credential store. Vifty stores only the opt-out preference, timestamps, an HTTP ETag, and the last validated release version in the private ~/Library/Application Support/Vifty/software-update.json file. An empty private owner-lock file in the same directory ensures only one running Vifty instance owns this state and request lane. The lock is acquired atomically, keyed by its filesystem identity rather than a path spelling, and held by its open descriptor; an instance that loses ownership remains fail-closed until relaunch. Both files use descriptor-anchored, no-follow, crash-durable storage. Turning automatic checking off cancels scheduled automatic checks; it does not enable a different update channel.
Current source includes an operator-invoked bridge for a public archive that has already been downloaded manually. From a reviewed source checkout whose manifest has promoted that release to the single current publishedRelease, run:
scripts/install-vifty.sh --public-release-archive /absolute/path/Vifty-vX.Y.Z.zip
# or
make install-public-release PUBLIC_RELEASE_ARCHIVE=/absolute/path/Vifty-vX.Y.Z.zipThis bridge is deliberately narrower than a general installer and starts with v1.4.0; the historical v1.3.2 bundle lacks its root snapshot binding contract. It selects only .github/release-manifest.json publishedRelease, requires the exact canonical filename, pinned SHA-256, and verified signed tag, and rejects a candidate, historical release, direct .app, relative path, URL, or other archive. After that archive-level authority passes, safe bounded private extraction establishes a complete candidate-content binding. The public release verifier and independent extracted-bundle checks must then confirm the exact version/build, bundle identities, Developer ID TeamID, deep signature, notarization/stapling, and Gatekeeper result without skip flags before the candidate feeds the unchanged fail-closed app-replacement transaction, including a private per-destination lock, existing Auto/System preflight, authority freeze, post-swap verification, authenticated downgrade refusal, and rollback behavior without a second-destination fallback.
The bridge performs no network request and never chooses or downloads a release. The in-app advisory checker still opens only the locally constructed GitHub tag page; the user separately downloads the canonical archive and explicitly invokes the bridge from a trusted checkout. This is a manual/operator migration path for the first checker-aware public release and later recovery or verification work. It is not automatic update, silent installation, Sparkle, or evidence that API filename/size metadata authenticated an archive.
Use Sparkle 2 only if Vifty later adds an in-place installer. That work is separate from the advisory checker above. It must enter Vifty's documented app-replacement transaction rather than replacing the bundle independently, and it should be enabled only after the release flow can produce and verify all of these:
- a Developer ID signed, notarized, stapled
Vifty.app; - the daemon built with the release
VIFTY_XPC_ALLOWED_TEAM_ID; - a canonical
Vifty-v<version>.ziparchive andVifty-v<version>.zip.sha256; - an HTTPS
SUFeedURLappcast hosted from a stable project-controlled location; - an EdDSA-signed appcast generated from the protected release key;
- a committed public
SUPublicEDKeywhose private EdDSA key is stored outside the release host; SURequireSignedFeedandSUVerifyUpdateBeforeExtractionenabled for signed appcast/feed validation;- Sparkle's
generate_appcastoutput for the canonical release archive; scripts/verify-release-artifact.sh --team-id <TEAMID>passing before publication;- Homebrew checksum handoff after the same artifact is published;
- pre-replacement daemon quiescence, complete Auto/System proof, no active lease or manual marker, post-swap verification, and rollback through the same root-ledger transaction required by Vifty's existing app-replacement boundary.
If the signed appcast and replacement integration exist, wire Sparkle as an additive app feature:
- Add Sparkle as the app updater framework and copy
Sparkle.frameworkintoVifty.app/Contents/Frameworks/while preserving symlinks and executable permissions. - Reconcile Sparkle's commands and preferences with the existing Vifty Check for Updates... / Update to latest version advisory UI instead of exposing two ambiguous update channels.
- Keep
SUFeedURL,SUPublicEDKey, signed-feed settings, and automatic-check defaults in the app bundleInfo.plist, generated from release configuration rather than ad-hoc local defaults. - Extend release metadata validation to reject missing updater keys, non-HTTPS feeds, unsigned appcasts, missing Sparkle framework bundling, or updater-enabled unsigned/source-first artifacts.
- Extend artifact verification to confirm the published app contains the intended updater metadata and that the release notes keep source-first, unsigned-dev, Homebrew, and auto-update lanes separate.
Before calling in-place auto-update available:
- test the updater UI on an older Developer ID signed build against a newer notarized build;
- verify Sparkle refuses unsigned, wrong-key, wrong-version, and non-canonical artifacts;
- verify update install preserves the bundled helper, daemon plist,
viftyctl, schemas, app icon, and TeamID-gated XPC settings; - verify a failed update does not leave fan control in a forced state and does not bypass helper repair/status guidance;
- verify every update enters the same replacement transaction, Auto/System proof, daemon/helper authority boundary, rollback, and root-ledger recovery path as a manual replacement;
- verify source-first builds still omit
SUFeedURL,SUPublicEDKey, and signed-feed keys.