@@ -44,27 +44,40 @@ then lift the guard.
44443 . ** Arch mapping.** Feed targets are ` darwin-arm64 ` and ` darwin ` (Intel). Map to the arm64 / x64 zips
4545 respectively — ** never serve a cross-arch zip** .
46464 . ** Lift the guard.** Set ` LEVELCODE_UPDATE_FEED_SIGNED=1 ` on Elastic Beanstalk — ** only after 1–3** .
47- 5 . ** Notify-only Download button.** ` extensions/levelcode-updater/extension.js:96 ` is
48- ` feed.url || product.downloadUrl || base ` . Once ` feed.url ` is a raw ` .zip ` , that button would hand
49- users a zip instead of the release page. Reorder to prefer ` product.downloadUrl ` / the release page.
47+ 5 . ** Notify-only Download button.** ` extensions/levelcode-updater/extension.js:96 ` * was*
48+ ` feed.url || product.downloadUrl || base ` . Once ` feed.url ` is a raw ` .zip ` , that button hands users a
49+ zip instead of a page. Now ` U.downloadUrl(feed, product, base) ` — ` product.downloadUrl ` →
50+ ` feed.releaseNotesUrl ` → base, dropping ` feed.url ` from the human path entirely. See the Sequencing
51+ risk below: this gated the next * release* , not the flag flip.
5052
5153## Slices
5254
53- ** S1 — publish the signed zip (client).** Add the ` ditto ` + ` shasum ` step to ` make-dmg.sh ` (or a
54- ` make-update-zip.sh ` ), update ` docs/RELEASING.md ` , and upload ` LevelCode-<arch>.app.zip ` with the dmg.
55- * Ship this alone first — it is inert until the feed points at it.*
55+ ** S1 — publish the signed zip (client). ✅ done. ** Add the ` ditto ` step to ` make-dmg.sh ` , update
56+ ` docs/RELEASING.md ` , and upload ` LevelCode-<arch>.app.zip ` with the dmg.
57+ * Shipped alone first — it is inert until the feed points at it.*
5658
57- ** S2 — serve it (server).** Teach ` EditorReleaseFeed ` to pick the arch-matched asset + hash. Guard stays
58- on, so behaviour is unchanged; assert the new shape in ` spec/requests/api/updates_spec.rb ` .
59+ ** S2 — serve it (server). ✅ done. ** Teach ` EditorReleaseFeed ` to pick the arch-matched asset + hash.
60+ Guard stays on, so behaviour is unchanged; the new shape is asserted in ` spec/requests/api/updates_spec.rb ` .
5961
60- ** S3 — extension URL fix.** Reorder the Download preference so it never opens a raw zip.
62+ ** S3 — extension URL fix. ✅ done.** ` U.downloadUrl() ` in ` extensions/levelcode-updater/update.js ` , so
63+ the Download button can never open a raw zip.
6164
6265** S4 — flip the flag + verify.** Set ` LEVELCODE_UPDATE_FEED_SIGNED=1 ` , then run the end-to-end test below.
66+ * Blocked until a release actually carries the S1 zips* — every release cut before S1 resolves to
67+ ` installable: false ` , so flipping the flag against today's releases is inert (Squirrel still gets 204).
6368
6469## Risks (the ones that actually bite)
6570
66- - ** Sequencing.** Flipping the flag before S1–S2 makes things * worse* — Squirrel would download a web
67- page and fail loudly. S4 must be last.
71+ - ** Sequencing.** Two * independent* constraints — the second is easy to miss:
72+ - Flipping the flag before S1–S2 makes things * worse* : Squirrel would download a web page and fail
73+ loudly. S4 must be last. This is now enforced in code, not just documented — the controller requires
74+ a resolved entry to be ` installable ` , so an early flip still serves 204.
75+ - ** S3 gated the next RELEASE, not S4.** The notify-only extension is served a 200 * regardless* of
76+ ` LEVELCODE_UPDATE_FEED_SIGNED ` — see ` updates_controller.rb ` : `unless notify_only_client? ||
77+ (signed_feed? && rel[ : installable ] )` . So the moment any release carries an ` .app.zip` , ` feed.url`
78+ becomes that zip for the extension too. Publishing an S1-asset release with S3 unshipped would have
79+ pointed every "Download" button at a raw zip, flag or no flag. Unlike the constraint above, nothing
80+ in the code would have stopped it — hence ` U.downloadUrl() ` and its regression test.
6881- ** Signing-identity continuity.** Squirrel.Mac refuses an update whose Developer ID doesn't match the
6982 running app. ** Rotating or changing the signing cert breaks auto-update for every installed build** ,
7083 with no in-app recovery — those users must re-download manually. Treat the identity as long-lived.
0 commit comments