From 0dacb1d6cfcadb323eeac2c07dad68b77a4324ae Mon Sep 17 00:00:00 2001 From: Joseph Yaksich Date: Sun, 2 Aug 2026 07:12:34 +0000 Subject: [PATCH] fix: point the website's offline release fallback at 0.0.38 site/server.mjs serves RELEASE_FALLBACK whenever GitHub's release API is unreachable or rate limited. It was still pinned to v0.0.30 with that release's digests, so any API hiccup would quietly hand every visitor a build eight patches old from the download buttons - a failure that is invisible until someone reports installing the wrong version. Updates the tag and all six artifact digests to the verified 0.0.38 artifacts, and derives the download URLs from a single tag constant so the tag and the URLs cannot drift apart. Adds a regression test that fails when the fallback goes stale: the tag must equal package.json, all five desktop artifacts must be named for the shipping version, and all six digests must be present and distinct. The existing offline-metadata contract hardcoded 0.0.30 as its expected answer, which would have kept passing against a stale fallback forever, so it now derives the expected version from package.json too. Co-Authored-By: Claude Fable 5 --- site/server.mjs | 21 +++++++++++++-------- test/site.mjs | 37 ++++++++++++++++++++++++++++++++----- 2 files changed, 45 insertions(+), 13 deletions(-) diff --git a/site/server.mjs b/site/server.mjs index 5f73257..0e3e2f0 100644 --- a/site/server.mjs +++ b/site/server.mjs @@ -30,21 +30,26 @@ const ORIGIN = "https://1helm.com"; const REPO = "gitcommit90/1Helm"; const RELEASE_PAGE = `https://github.com/${REPO}/releases/latest`; const RELEASE_CACHE_MS = 10 * 60_000; +// Served only when GitHub's release API is unreachable or rate limited. It +// must name the current release: a stale fallback silently hands visitors an +// older build from the download links. Update the tag and all six digests in +// the same commit that ships a release. +const RELEASE_FALLBACK_TAG = "v0.0.38"; const RELEASE_FALLBACK = { - tag_name: "v0.0.30", + tag_name: RELEASE_FALLBACK_TAG, draft: false, prerelease: false, assets: [ - ["1Helm-0.0.30-arm64.dmg", "597bccdf464e397389511100f254b40b3da266a52e00049d4553bf5476440bcb"], - ["1Helm-0.0.30-mac-arm64.zip", "54729c10aee1173f1f4b085826bdfe9838f37dfc61f3999ff8d073e39ccc4e94"], - ["1Helm-0.0.30-linux-node.tgz", "d96cb1bbc73686562dbff3c797cbd8fcc8962c8cc97602edd1fabf1b1e2e5d64"], - ["1Helm-0.0.30-windows-x64-setup.exe", "ff5b2fa32ea916b88dc08164bb52a55253d9921f73f713b1413bbf4ac1d4669f"], - ["1Helm-0.0.30-full.nupkg", "3142069585e3a02290fc8092ff8e1e80683c355aa1e78d2871260f1518ea83bf"], - ["RELEASES", "11bc1746bca8f6414f8dd6df254bfcf921dec13ca72b037c03c47152db30f542"], + ["1Helm-0.0.38-arm64.dmg", "468a5b8d59a23c4419331db20dcdb955f088a046add48296b5d5aa8450120527"], + ["1Helm-0.0.38-mac-arm64.zip", "4135defe285f9d7e480802d17d81ff37d083a90f6d7dc928f7061a73f9861302"], + ["1Helm-0.0.38-linux-node.tgz", "12d59534b66c325891d5433eb2c7ab8990ee67c9d56a805b65b9633030c424fc"], + ["1Helm-0.0.38-windows-x64-setup.exe", "0689afadc996319a22d6a3419d39725453bc46d9bdd8857463a56b7bcf738590"], + ["1Helm-0.0.38-full.nupkg", "2b25a71f6b3bbb96d7a62cca4bdbd421daed22a1005b1e93d31a77b3d61e86ae"], + ["RELEASES", "a20bf26fc174a48a2ccd30c4b7ab0289d72c00489761981c89a7eb3c9842f454"], ].map(([name, digest]) => ({ name, digest: `sha256:${digest}`, - browser_download_url: `https://github.com/${REPO}/releases/download/v0.0.30/${name}`, + browser_download_url: `https://github.com/${REPO}/releases/download/${RELEASE_FALLBACK_TAG}/${name}`, })), }; const FEEDBACK_DATA_DIR = resolve(process.env.SITE_DATA_DIR || join(ROOT, ".site-data")); diff --git a/test/site.mjs b/test/site.mjs index 718370e..428ec2f 100644 --- a/test/site.mjs +++ b/test/site.mjs @@ -112,11 +112,14 @@ test("release metadata stays available when GitHub's unauthenticated API is exha await waitFor(`${base}/health`); const response = await fetch(`${base}/api/releases/linux/latest`); assert.equal(response.status, 200); - assert.deepEqual(await response.json(), { - version: "0.0.30", - url: "https://github.com/gitcommit90/1Helm/releases/download/v0.0.30/1Helm-0.0.30-linux-node.tgz", - sha256: "d96cb1bbc73686562dbff3c797cbd8fcc8962c8cc97602edd1fabf1b1e2e5d64", - }); + // Derived from package.json rather than pinned: the point of this contract + // is that the offline fallback serves the SHIPPING release, so hardcoding a + // version here would keep passing while the fallback silently went stale. + const version = JSON.parse(readFileSync(join(root, "package.json"), "utf8")).version; + const offline = await response.json(); + assert.equal(offline.version, version, "the offline fallback serves the shipping version"); + assert.equal(offline.url, `https://github.com/gitcommit90/1Helm/releases/download/v${version}/1Helm-${version}-linux-node.tgz`); + assert.match(offline.sha256, /^[a-f0-9]{64}$/, "the offline fallback carries a real digest"); } finally { child.kill("SIGTERM"); await new Promise((resolve) => child.once("exit", resolve)); @@ -305,3 +308,27 @@ test("autonomy report names its deterministic scope and live-system limits", () assert.match(report.scope.validates.join(" "), /wakeable recurring-work persistence/); assert.match(report.scope.does_not_validate.join(" "), /live model or provider/); }); + +test("the website's offline release fallback names the shipping version", () => { + // This fallback is served when GitHub's release API is unreachable or rate + // limited. A stale entry does not fail loudly — it quietly hands every + // visitor an older build from the download links, which is exactly how a + // release goes out with the previous version behind the buttons. + const version = JSON.parse(readFileSync(join(root, "package.json"), "utf8")).version; + const server = readFileSync(join(root, "site", "server.mjs"), "utf8"); + const block = server.match(/const RELEASE_FALLBACK = \{[\s\S]*?\n\};/)?.[0]; + assert.ok(block, "site/server.mjs still exposes a release fallback block"); + assert.match(server, new RegExp(`RELEASE_FALLBACK_TAG = "v${version.replaceAll(".", "\\.")}"`), "the fallback tag matches package.json"); + for (const asset of [ + `1Helm-${version}-arm64.dmg`, + `1Helm-${version}-mac-arm64.zip`, + `1Helm-${version}-linux-node.tgz`, + `1Helm-${version}-windows-x64-setup.exe`, + `1Helm-${version}-full.nupkg`, + ]) { + assert.ok(block.includes(asset), `the release fallback names ${asset}`); + } + const digests = [...block.matchAll(/"([a-f0-9]{64})"/g)].map((m) => m[1]); + assert.equal(digests.length, 6, "all six desktop artifacts carry a fallback digest"); + assert.equal(new Set(digests).size, 6, "no two fallback digests are duplicated"); +});