Skip to content

Gate releases on a verified asset manifest; source download claims from the release feed - #259

Merged
sarthakagrawal927 merged 3 commits into
mainfrom
fix/release-assets-gate-253
Sep 6, 2026
Merged

sarthakagrawal927 merged 3 commits into
mainfrom
fix/release-assets-gate-253

Conversation

@sarthakagrawal927

Copy link
Copy Markdown
Member

Refs #253.

auto-release.yml created the GitHub release before any build ran and exited without waiting, so all eight recent runs reported success — including the six that produced asset-less releases (v1.12.0..v1.13.2). scripts/verify-release-manifest.mjs existed but was referenced by no workflow and no package script.

Release gate

  • The release is a draft for the whole build. auto-release.yml now creates the v<version> tag explicitly (a draft release does not create its own tag, and both release.yml and the qualification workflow check out the exact tag) and then a draft release. A draft is absent from /releases/latest, from the release feed the download page reads, and from Sparkle's feed — so a failed build leaves nothing user-visible behind.
  • verify-release-manifest.mjs is the required gate. Rewritten from a network-only appcast probe into a manifest verifier with a pure, testable core. It fails when a contract asset is missing, empty, still uploading, or attached under a name that is not on the contract, and emits a codevetter.release-manifest-verification/v1 receipt.
  • It runs twice: in release.yml after upload and before the draft is flipped to published, and again in auto-release.yml after the watched run finishes.
  • auto-release.yml now waits. It resolves the dispatched release.yml run and gh run watch --exit-statuses it, so a green auto-release run means a downloadable release exists.
  • Cryptographic appcast inspection stays where it is (inspect-native-appcast.mjs, offline, inside qualification); this gate only asserts the published manifest, so nothing is duplicated.
  • Unchanged: signing, notarization, stapling, every secret, and both workflows' triggers (push on Shared.xcconfig + workflow_dispatch; release: created + workflow_dispatch).

The old ad-hoc grep -Fx contract check and the curl of the public feed are replaced by the script plus a post-publish feed check with retries.

Download page (#253 items 2 and 3)

/download and /download.md no longer hardcode a version, a filename, or an update promise. src/data/release.ts reads the release feed at build time and picks the newest published release that actually carries an installer, so the page can only state what is downloadable. If the feed is unreachable or rate-limited the build fails soft: it links the releases index and claims no filename, rather than claiming something stale.

The Sparkle claim is now conditional on a published appcast.xml. No release carries one, so both surfaces currently say updates are manual — which matches NativeUpdaterConfiguration.ready and evidence/verification/native-release-readiness-2026-09-02.md. The claim turns itself back on the first time a release publishes an appcast. The appcast is real and is generated, EdDSA-signed and inspected in native-production-qualification.yml; its feed URL is https://github.com/Codevetter/codevetter/releases/latest/download/appcast.xml, not codevetter.com/appcast.xml — nothing was invented here.

Built output today:

The current public artifact is a signed and notarized Apple-silicon macOS DMG, published through GitHub Releases. CodeVetter requires no CodeVetter account. 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.

Download v1.11.1 → · macOS — Apple Silicon · CodeVetter_1.11.1_aarch64.dmg

Evidence

Dry-run of the gate against real releases:

$ node scripts/verify-release-manifest.mjs --tag v1.11.1   # exit 1
missing:    CodeVetter-1.11.1-arm64.dmg, CodeVetter-1.11.1-arm64.zip, appcast.xml
unexpected: CodeVetter.app.tar.gz.sig, CodeVetter_1.11.1_aarch64.dmg,
            CodeVetter_aarch64.app.tar.gz, latest.json
blockers:   installer_published, update_archive_published, appcast_published,
            assets_uploaded, assets_on_contract

$ node scripts/verify-release-manifest.mjs --tag v1.13.2   # exit 1
blockers: installer_published, update_archive_published, appcast_published, assets_uploaded

$ node scripts/verify-release-manifest.mjs --tag v1.13.3 --manifest complete.json
status: qualified                                          # exit 0

So the gate blocks both failure modes #253 reported — Tauri-shaped filenames and zero assets — and passes a complete manifest.

Local gates: pnpm lint, pnpm knip:strict, quality:change-size, quality:complexity, quality:cycles, quality:duplication, pnpm test:release-manifest (10 tests), pnpm test:automation, node scripts/check-docs.mjs, landing pnpm build + verify-agent-surfaces.mjs (6/6 PASS) — all green. Workflow shell fragments shellcheck clean locally; actionlint runs in repository-security.yml on this PR.

Not done here

No tag, no release, nothing published. The pipeline is still unproven end to end — see the comment on #253 for the single maintainer action that proves it.

🤖 Generated with Claude Code

…laims from the feed

Refs #253

`auto-release.yml` published the GitHub release before any build ran and
exited without waiting, so all eight recent runs reported success —
including the six that produced asset-less releases (v1.12.0..v1.13.2).
`scripts/verify-release-manifest.mjs` was wired to nothing.

Release gate:
- the release is now created as a draft on an explicitly created tag, so a
  failed build leaves nothing on /releases/latest or the download page;
- `verify-release-manifest.mjs` is rewritten as the required gate: it fails
  when a contract asset is missing, empty, still uploading, or attached
  under an off-contract name, and it runs in `release.yml` before the draft
  is published and again in `auto-release.yml` after the watched run;
- `auto-release.yml` waits on the dispatched `release.yml` run, so a green
  run means a downloadable release exists;
- signing, notarization, secrets, and both workflows' triggers are unchanged.

Download page:
- /download and /download.md now read the tag, installer filename, update
  archive, and appcast presence from the GitHub release feed at build time
  instead of hardcoding them, and fall back to claiming nothing when the
  feed is unreachable;
- the Sparkle auto-update claim is now conditional on a published
  appcast.xml. No release carries one, so both surfaces say updates are
  manual, matching `NativeUpdaterConfiguration.ready`.

Tests: `scripts/verify-release-manifest.test.mjs` (complete manifest, draft,
missing asset, no assets, Tauri-shaped names, wrong version, empty/pending
upload, bad tag), wired into CI as `pnpm test:release-manifest`.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Comment thread .github/workflows/release.yml Fixed
sarthakagrawal927 and others added 2 commits September 6, 2026 06:26
The runner's preinstalled Node runs verify-release-manifest.mjs, which uses
only stable built-ins, so setup-node's dependency cache added a
cache-poisoning surface to a workflow that can publish releases for no gain.

Refs #253

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A draft is not reliably resolvable by `gh release view <tag>`, and GitHub
will create a second draft under a tag that already has one. Matching the
full release list keeps a re-run after a failed build a true no-op.

Refs #253

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@sarthakagrawal927
sarthakagrawal927 merged commit 14ee910 into main Sep 6, 2026
24 checks passed
sarthakagrawal927 added a commit that referenced this pull request Sep 6, 2026
Refs #252, #253.

No product change. This bump exists to run the release gate landed in
#259 end to end, which no run has ever done: the last six releases
(v1.12.0..v1.13.2) went public with zero assets while every
auto-release run reported success.

What this release exercises:

- auto-release.yml creates the tag and a DRAFT release, dispatches
  release.yml, waits on that run, and re-verifies the published asset
  manifest, so a green run means a downloadable release exists;
- release.yml verifies the manifest with verify-release-manifest.mjs
  before flipping the draft to published, and checks that the public
  appcast URL resolves;
- the installed-upgrade proof's incumbent is pinned to v1.11.1, whose
  bundled CLI has the `rubrics` command the proof seeds data with
  (#252/#258);
- /download and /download.md read the tag, installer filename, update
  archive, and appcast presence from the release feed at build time,
  so the page follows whatever this release actually publishes;
- src/data/privacy.ts stays the single source for /privacy,
  /privacy.md, and the page JSON-LD.

The three crates and their exact path pins move with the app version,
as 1.13.1 established; core:qualify-cli reports 1.13.3.

Local gates: lint, knip:strict, change-size, complexity, cycles,
duplication, check-docs, test:release-manifest, core:qualify-cli.

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
@sarthakagrawal927
sarthakagrawal927 deleted the fix/release-assets-gate-253 branch September 6, 2026 12:27
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.

2 participants