Skip to content

Download page: latest releases ship no assets, filename pattern is wrong, and the Sparkle update claim is unsubstantiated #253

Closed

Description

@sarthakagrawal927

Writing the rewrite of codevetter.com/download surfaced three claims on the live page that do not currently hold. Filing together because they share one likely root cause: the cutover to the native SwiftUI release pipeline.

1. The six newest releases ship no downloadable build

v1.12.0 through v1.13.2 — six releases cut between 2026-09-04 23:11Z and 2026-09-05 11:10Z — all have zero release assets.

The newest release carrying a DMG is v1.11.1 (2026-09-03). Every release from v1.5.3 onward carries four assets, so this is a regression rather than intent.

The /download page's "Latest release →" link therefore sends visitors to a release with nothing to download.

PROJECT_STATUS.md:43 describes auto-release.yml cutting a v<version> tag on Shared.xcconfig version bumps while release.yml signs, notarizes, qualifies and uploads. That split is consistent with tags being cut while the upload job does not complete.

2. The documented filename pattern matches no published asset

/download states the artifact is CodeVetter-<version>-arm64.dmg.

Every published asset is CodeVetter_<version>_aarch64.dmg — underscores, and aarch64 rather than arm64. Anyone scripting against the documented name gets nothing.

3. The Sparkle auto-update claim is contradicted by the repo's own evidence

/download states installed copies "update through the in-app Sparkle appcast".

  • https://codevetter.com/appcast.xml → 404
  • evidence/verification/native-release-readiness-2026-09-02.md:28 — "no production HTTPS Sparkle appcast is configured"
  • crates/codevetter-core/src/capabilities.rs:298 — "Sparkle is locally packaged but remains disabled until production signing, appcast, EdDSA, and installed-upgrade gates pass"
  • crates/codevetter-core/src/capabilities.rs:666 — "Sparkle remains inactive unless a production HTTPS appcast and EdDSA public key are present"

On the evidence available, auto-update is not live. I could not disprove that it was enabled in the days since 2026-09-02 — there is no appcast at the conventional URL and nothing in the repo points at another one. Someone with the deploy view should confirm.

This is the most expensive of the three to leave standing: a download page asserting an update mechanism that the product's own verification evidence says is disabled sits badly against "Review suggests. Evidence decides."

Suggested fixes

  • Make release.yml fail loudly when a tag is cut without assets uploaded, so this cannot regress silently again.
  • Correct the filename pattern on /download, or template it from the release feed.
  • Either enable the production appcast or change the page to say updates are manual for now.
  • Template the version string on /download rather than hardcoding — six releases landed in under 13 hours.

Found while drafting SEO/GEO content against the live surface. Nothing has been published; the draft is held pending these.

Activity

  1. sarthakagrawal927 commented on Sep 6, 2026

    @sarthakagrawal927
    MemberAuthor

    Took the two mechanical parts of this. #259 is merged (all 22 checks green, squashed as 14ee910e).

    What is fixed

    Item 1 — the pipeline can no longer report success without shipping a build.

    The root cause was upstream of release.yml: auto-release.yml created the published release before any build ran and exited without waiting, so the release existed the moment the tag was cut and the run went green regardless of what happened next. All eight recent auto-release.yml runs reported success, including the six that produced asset-less releases. The only asset check lived inside release.yml after a successful upload, and scripts/verify-release-manifest.mjs was referenced by no workflow and no package script at all.

    Three changes:

    • the release is created as a draft on an explicitly created tag, so a failed build leaves nothing on /releases/latest, in the release feed, or on the download page;
    • verify-release-manifest.mjs is rewritten as the required gate and now runs twice — in release.yml before the draft is flipped to published, and again in auto-release.yml after the build. It fails when a contract asset is missing, empty, still uploading, or attached under a name that is not on the contract;
    • auto-release.yml waits on the dispatched release.yml run, so a green auto-release run means a downloadable release exists.

    Signing, notarization, secrets, and both workflows' triggers are untouched.

    Items 2 and 3 — the page no longer holds claims that can drift.

    /download and /download.md read the tag, the installer filename, the update archive, and appcast presence from the GitHub release feed at build time. The filename is no longer written down anywhere on the page, so it cannot be wrong; the version is no longer hardcoded; and the "Latest release" button points at the newest release that actually carries an installer rather than at v<MARKETING_VERSION>. If the feed is unreachable the build falls back to claiming nothing.

    The Sparkle claim is now conditional on a published appcast.xml. None exists, so both surfaces currently read:

    Installed copies do not update themselves yet: no published release carries the signed Sparkle appcast the in-app updater requires, so update by downloading the newest release.

    Live page today: Download v1.11.1 →, CodeVetter_1.11.1_aarch64.dmg.

    On your open question — the appcast is real, it just has never been published. native-production-qualification.yml generates it with Sparkle's generate_appcast, EdDSA-signs it, and cryptographically inspects it; release.yml uploads it as a release asset. The feed URL the app is built with is https://github.com/Codevetter/codevetter/releases/latest/download/appcast.xml, not codevetter.com/appcast.xml — so the 404 you found is expected and is not the feed. Nothing was invented: the page states the mechanism is inactive, and it will turn itself back on the first time a release publishes an appcast.

    What a maintainer must do to prove it

    Everything above is verified against fixtures and against the existing release feed, but the pipeline itself is still unproven end to end — no run has ever reached the upload step. As #252 notes, each run since the native cutover failed at a different, later gate. The next release attempt is the test, and only a maintainer can run it.

    To prove it:

    1. Bump MARKETING_VERSION in apps/macos/Config/Shared.xcconfig to the next patch and merge to main.
    2. Watch the single auto-release.yml run. It now blocks until release.yml finishes, so its conclusion is the whole answer.
    3. Green means: the release left draft state, carries exactly CodeVetter-<version>-arm64.dmg, CodeVetter-<version>-arm64.zip, and appcast.xml, and the public appcast URL resolves. The release-manifest-verification artifact on the release.yml run holds the receipt.
    4. Red means the release is still a draft and nothing went public. Read the watched release.yml run — the failing gate names itself. Re-running auto-release.yml will no-op on the existing draft, which is deliberate.

    Then redeploy the landing page (gh workflow run deploy-landing.yml --ref main) so /download picks up the new release and flips the update copy to the Sparkle wording on its own.

    Worth deciding separately: the six asset-less releases v1.12.0..v1.13.2 are still public and v1.13.2 is still what /releases/latest resolves to. Deleting those six releases (keeping or dropping the tags) would make /releases/latest honest immediately, ahead of the next release. I have not touched them.

    Leaving this open — the download-page copy and the gate are both in, but the first claim in this issue stays literally true until a release publishes assets. Close it on the run in step 2.

  2. sarthakagrawal927 commented on Sep 6, 2026

    @sarthakagrawal927
    MemberAuthor

    Ran the proof in your step 2. The gate works — it failed closed, and nothing went public. Assets still did not publish, so this issue stays open; the failure is #252's gate, not the release gate.

    The run

    What the new gate did, step by step

    # auto-release step Result
    5 Create release tag pass — v1.13.3 → 9dacf597
    6 Create draft GitHub release pass — draft
    7 Dispatch release build workflow pass
    8 Wait for the signed build to publish assets failure — the watched run failed
    9 Verify the published release asset contract skipped

    Every claim in your step 4 held:

    • the release is still a draft (draft=true, 0 assets), reachable only from the authenticated list — it has no public tag page yet, it renders as untagged-15c25cc88390e82bfae3;
    • /releases/latest is unchanged at v1.13.2 — the failed attempt added nothing to the release feed and nothing to /download;
    • auto-release.yml reported failure, not success. That is the specific defect this issue opened on: the previous version of this workflow would have reported success here, exactly as it did for the six asset-less releases.

    So the half of the contract that can be proven by a red run is proven. The half that needs a green run is not, because the build never reached the upload step.

    Why the build failed

    Not signing, not notarization, not Sparkle, not a secret — all of those passed. Step 17, the installed-upgrade proof, failed at the first GUI launch of the retained Tauri incumbent:

    Application 62867 did not show a visible window within 25000 ms
    

    Full analysis in #252, which is now the sole remaining blocker: its original unknown command rubrics cause is fixed — the v1.11.1 pin seeds the rubric correctly and the gate advanced two steps past it before failing on window registration.

    I left the draft release and the v1.13.3 tag exactly as the pipeline left them, and did not publish or delete either.

    On the six asset-less releases

    Listed, not touched:

    Tag Published Assets
    v1.13.2 2026-09-05 11:10Z 0
    v1.13.1 2026-09-05 10:44Z 0
    v1.13.0 2026-09-05 10:34Z 0
    v1.12.2 2026-09-04 23:50Z 0
    v1.12.1 2026-09-04 23:32Z 0
    v1.12.0 2026-09-04 23:11Z 0

    Recommendation: delete the six releases, keep the six tags. An asset-less release page is a dead end for anyone who lands on it from the releases index, and v1.13.2 being /releases/latest makes the newest thing we advertise the emptiest. Keeping the tags costs nothing, keeps those commits reachable, keeps git describe honest, and keeps this incident verifiable. Nothing depends on those six releases existing: the qualification pins v1.11.1 as the incumbent, and src/data/release.ts already skips releases with no installer, so /download behaves identically before and after.

    Not urgent either way — it stops mattering the moment a release publishes assets, which is the same event that closes this issue.

  3. sarthakagrawal927 commented on Sep 6, 2026

    @sarthakagrawal927
    MemberAuthor

    Blocker (2026-09-06): in-progress: agent — needs a maintainer to cut the next release and watch auto-release.yml go green end to end; the fix is merged and waiting on that real run.

  4. sarthakagrawal927 commented on Sep 6, 2026

    @sarthakagrawal927
    MemberAuthor

    Correcting the root cause on this issue, because the earlier reading was wrong in a way that matters.

    release.yml was firing. auto-release.yml already dispatched it explicitly with gh workflow run — the anti-recursion safeguard was handled long before #259. All six broken releases did dispatch release.yml, and all six did fail in the build. The regression was never a missing trigger; it was the qualify job failing, which skips publish, which is why no assets were ever attached.

    The real cause: the last gate before upload has never passed, on any machine. scripts/qualify-native-installed-upgrade.mjs asks System Events for count windows of first process whose unix id is <pid>. whose binds to the nearest preceding collection, so that filters windows by unix id — a property only processes carry. Every poll answers -1728 "Can't get unix id of window", the catch swallows it as "not registered yet", and the launch times out unconditionally.

    Reproduced locally, both forms polled against the same live pid: the current form errors on every poll for 40s; the parenthesized form returns a count, and returns 1 against an app that genuinely has a window.

    Why it hid for six releases. The proof runs only in native-production-qualification.yml, which runs only on a release. So each release cut surfaced exactly one more latent failure rather than all of them:

    Release Run Failed at
    v1.12.1 33929885965 step 13 — rebuild archives
    v1.12.2 33930948063 step 15 — Sparkle appcast
    v1.13.2 33962630311 step 17 — rubrics missing from the v1.11.0 incumbent
    v1.13.3 34029077434 step 17 — this bug

    Fix: #263. It also makes the failure legible — the run above produced 25 seconds of identical -1728 lines and nothing else — by retaining the app's output, separating "could not read the process" from "reported no windows", and giving a cold GUI launch on a loaded runner more than 25s.

    Keeping this open: assets are not live yet. v1.13.3 is currently a draft with zero assets, which is the #259 gate correctly refusing to publish an assetless release — a real improvement over v1.12.0–v1.13.2, which published as Latest with nothing attached.

  5. sarthakagrawal927 commented on Sep 8, 2026

    @sarthakagrawal927
    MemberAuthor

    The public download mismatch is fixed. codevetter.com/download and /download.md now serve v1.13.7, matching the published DMG and ZIP names. The checked landing build is f051219; Pages fc868277-6431-4f8d-b246-e2ecbd596bf0 and Worker ef571fd9-51ac-4559-91fb-c21339cd650b are deployed, with the Worker at 100% traffic.

    Downloaded release run 34095571484 evidence confirms Developer ID notarization/Gatekeeper acceptance, configured HTTPS GitHub Releases appcast, EdDSA signature verification, and isolated installed upgrade/relaunch/rollback with rubric preservation. The public ZIP digest matches the qualified archive. The configured feed is https://github.com/Codevetter/codevetter/releases/latest/download/appcast.xml, not the old site-root URL.

    Remaining: the appcast receipt explicitly excludes installed updater behavior. Keep this issue open until the actual in-app Sparkle download/install/relaunch path is exercised, or public copy is narrowed to the verified update availability. Manual hosted replacement is not proof of Sparkle installation.

  6. sarthakagrawal927 commented on Sep 8, 2026

    @sarthakagrawal927
    MemberAuthor

    The patched website is now deployed from merged main e6a3b44 after all six deploy gates passed. Pages preview: https://961fb962.codevetter.pages.dev. Worker version ab4401c1-9768-4c12-8d19-c35fb7977acb is assigned 100% traffic in deployment c10d29e4-9ebb-4948-a3f4-9a262b291036, with the exact source SHA tag verified. Live /download and /download.md return 200 and point to CodeVetter-1.13.8-arm64.dmg; /docs/ and /privacy also return 200. The configured GitHub appcast returns 200 with version 1.13.8 and an EdDSA signature. Actual in-app Sparkle download/install/relaunch remains unqualified; this issue stays open for that remaining behavior.

  7. sarthakagrawal927 commented on Sep 9, 2026

    @sarthakagrawal927
    MemberAuthor

    Additional public installer verification for v1.13.9:

    • DMG SHA-256 f0478d4c993e285e02d43d135b08df73b86bf05601dda3e32b0fe9c86dccebae matches GitHub asset digest.
    • Disk-image checksum verification passes. Mounted read-only without opening Finder or launching the app.
    • DMG app passes strict deep codesign verification and Gatekeeper assessment (Notarized Developer ID).
    • App executable, bundled CLI and Info.plist hashes match the independently verified public ZIP.
    • The disk image contains CodeVetter.app only: there is no Applications shortcut. Manual installation requires opening Applications separately. This is a packaging polish limitation, not evidence of a failed app installation.
    • Task-owned disk image was detached successfully. Owner installation and data were not modified.

    PR #279 now builds complete website/docs output with actual 1.13.9 asset names and explicit manual-update guidance. CI is still running; deployment remains pending.

  8. sarthakagrawal927 commented on Sep 9, 2026

    @sarthakagrawal927
    MemberAuthor

    Resolved the three download-page defects with published-package and live-site evidence.

    • v1.13.9 is public with DMG, ZIP and signed appcast. The actual public DMG/ZIP digests, app signature and Gatekeeper checks pass; real packaged Codex checks distinguish passing correctness, genuine failure and unavailable performance evidence.
    • PR docs: scope updater claims to verified behavior #279 merged as 12b9343. All PR checks and all three exact-main push workflows passed; the deploy guard passed six gates.
    • Website deployed to Pages https://90936fbc.codevetter.pages.dev and Worker version 45c3f6e6-9021-4801-b3bb-bde6bf6fda77. Active Worker traffic is 100% with the full merged SHA tag.
    • Live /download and /download.md both name CodeVetter-1.13.9-arm64.dmg and explicitly say in-app installation/relaunch are still being verified. /docs/ and /privacy respond successfully.

    This implements the issue's suggested manual-update alternative. Actual Sparkle installation is NOT claimed qualified: the unresolved requirement remains explicitly retained in README under Verify installed Sparkle updates. The missing Applications shortcut in the DMG is also a known packaging polish limitation; users can open Applications separately. Closing the original broken-download/misleading-claim issue, not asserting whole-product production readiness.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    in-progress: agentAn agent is actively working it today.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions