Repository navigation
fix(downloads): serve installers from ScientFactory's mirror and never dead-end a download - #74
Merged
Conversation
…r dead-end a download A download click resolved the release by asking GitHub for the handoff whenever the data center's five-minute cache was cold. Any refusal or error ended in a JSON 503. Production failures came in ~0.3 s bursts of simultaneous requests against an expired cache; GitHub's status was never recorded. - The new scientfactory-release-mirror worker copies each Stable installer into the scient-downloads R2 bucket (downloads.scientfactory.com) every ten minutes. R2 verifies the handoff's SHA-256 on write; the Stable pointer is written last. - Downloads use the mirror while the worker has confirmed it within six hours, then GitHub (one retry for timeouts, network errors and 5xx; none for 4xx), then the mirror however old. - When nothing resolves, the visitor is redirected to the GitHub latest-release page instead of a 503, and download_failed records GitHub's status or error name, elapsed time, attempts and the fallback. - The trusted destination policy accepts the mirror's versioned installer paths only.
Deploying scientfactory-website with
|
| Latest commit: |
249a026
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://8c2cc81b.scientfactory-website.pages.dev |
| Branch Preview URL: | https://fix-download-release-resilie.scientfactory-website.pages.dev |
…er only over what this run saw Review findings on #74: - An installer key already holding other bytes is refused instead of overwritten, and a new copy is written with If-None-Match: *, so published bytes (and year-long caches of them) never change under a tag. - The Stable pointer is written with a compare-and-swap on the version this run observed, so a slower run that saw an older release cannot replace a newer promotion.
A run whose handoff answer was delayed past another run's promotion read the pointer etag afterwards, so its compare-and-swap succeeded and rolled the pointer back. The etag is now captured first; the regression pauses the older run's handoff response.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
/api/download/:assetresolved the release by fetching GitHub's latest handoff whenever the data center's five-minute edge cache was cold. Any refusal, error or timeout becamerelease_fetch/upstream_unavailableand a bare JSON 503 with no way forward.Production: 15
download_failedevents (Oct 15 – Nov 12); on Nov 12, 7 failures vs 17 clicks.Root cause (agreed independently by Claude and Codex)
Proven: the click path depended on a live, unauthenticated GitHub request on every cold cache, with no durable fallback and no recovery link. Pages Functions invocation analytics for the failure bursts (Nov 12 00:41:37, 05:23:54; Nov 10 14:22:39) show each failed request finished in ~0.3 s with two subrequests. These were fast refusals or errors from GitHub, not timeouts. They arrived as bursts of simultaneous requests against an expired cache, where some requests failed and others in the same burst succeeded.
Not proven: which GitHub response it was. The status and error were discarded, and Pages logs do not persist. 30-way concurrent fetches from Cloudflare's edge (TLV) and local
curlall succeeded today. Codex rates the specific upstream cause low-confidence. This fix removes the dependency instead of tuning around one guessed status, and it records the status from now on.Change
Mirror worker (
workers/release-mirror,scientfactory-release-mirror, cron every 10 min):scient-downloadsR2 bucket (downloads.scientfactory.com), atscient-desktop/<tag>/<name>.scient-desktop/stable.jsonlast. A failed run leaves the previous pointer.Resolution order:
All sources pass
parseReleaseHandoff.No dead end: if nothing resolves, the response is a 302 to
github.com/ScientFactory/scient-desktop/releases/latest.download_failednow recordsupstream_status,upstream_error,upstream_elapsed_ms,upstream_attemptsandfallback.Destination policy: besides the canonical GitHub path, it accepts only
https://downloads.scientfactory.com/scient-desktop/v<semver>/<installer>, with no query string.Pages: binds
DESKTOP_DOWNLOADStoscient-downloadsinwrangler.jsonc.Docs:
docs/architecture/desktop-downloads.md.Evidence
bun run checkpasses: format, typecheck, events and mirror types and typecheck, 179 tests, build, edge build.wrangler dev --remote --test-scheduled, against a temporary bucket that has since been deleted):Scient-0.6.28-x64.exeSHA-256a9783a37…afb07cequals the handoff's digest.Deployment
bun run mirror:deploydeploys the worker. Its first cron populatesscient-desktop/inscient-downloads.HEAD /api/download/<asset>returns 302 todownloads.scientfactory.comonce the 5-minute edge cache turns over.Rollback: revert the PR. The worker can be deleted; mirrored objects are inert.
Owner authorized merge in the conversation (investigate with Codex, fix, Codex review, then merge).
Documentation impact: Updated —
docs/architecture/desktop-downloads.md