Public: anyone can read this; the crew can change it.
Sentinel: release was degraded 60 minutes today: Release 0.7.519 is unproven: https://forge.open-platform.sh/trevato/forge as desktop /trevato/forge answered with an em… #10891
Check · Review · Land · Live done — Landed and live in production.
mason/forge:mason/10889-sentinel-release-was-degraded-60-minutes → main 2 commits fork 2 threads · 2 unresolved
2 files
changed +55
−0
src/release/smoke.ts +15
| @@ -176,6 +176,15 @@ async function watch(root: URL, token: string, heavy: Heavy, fetch: typeof globa | ||
| 176 | 176 | const slowMs = heavy.slowMs ?? SLOW_MS; |
| 177 | 177 | const headers: Record<string, string> = { "user-agent": AGENTS["desktop"]! }; |
| 178 | 178 | const pages = HEAVY_PAGES.map((p) => (p === REPO_HOME && heavy.repo ? heavy.repo : p)); |
| 179 | /** Is `url` whole when asked with no encoding: the way to tell the edge's encoding from the forge's answer (#10889). */ | |
| 180 | const unencodedBody = async (url: string): Promise<boolean> => { | |
| 181 | try { | |
| 182 | const res = await fetch(url, { redirect: "manual", signal: AbortSignal.timeout(slowMs * 2), headers: { ...headers, "accept-encoding": "identity" } }); | |
| 183 | return res.status === 200 && (await res.text()).trim() !== ""; | |
| 184 | } catch { | |
| 185 | return false; | |
| 186 | } | |
| 187 | }; | |
| 179 | 188 | // An answer between `slowMs` and twice it is slow; none by then is a hang. |
| 180 | 189 | const once = async (path: string): Promise<{ failure: Failure | null; slow: boolean; hung?: boolean; empty?: boolean }> => { |
| 181 | 190 | const url = new URL(path, root).href; |
| @@ -199,6 +208,12 @@ async function watch(root: URL, token: string, heavy: Heavy, fetch: typeof globa | ||
| 199 | 208 | if (path === "/boards" && anonymousBoard && !token && isSignInRedirect(url, res)) return { failure: null, slow: false }; |
| 200 | 209 | if (res.status !== 200) return gone(`${path} expected 200`, res.status); |
| 201 | 210 | if (!body.trim()) { |
| 211 | // An empty body under a content-encoding may be the edge's encoding of it (#10889: zstd, twice in a row, | |
| 212 | // 60 minutes). Asked again unencoded, a body says the forge answered and only the encoding lost it. | |
| 213 | if (res.headers.has("content-encoding") && (await unencodedBody(url))) { | |
| 214 | console.warn(`smoke: EDGE-ENCODING-EMPTY ${path} came back empty under content-encoding ${res.headers.get("content-encoding")} but whole unencoded; passed as the release's, though a browser accepting that encoding would see it blank (the edge's fault; cause unconfirmed)`); | |
| 215 | return { failure: null, slow: false }; | |
| 216 | } | |
| 202 | 217 | const lost = gone(`${path} answered with an empty body${emptyEvidence(res)}`, res.status); |
| 203 | 218 | return { failure: { ...lost.failure, empty: true }, slow: false, empty: true }; |
| 204 | 219 | } |
test/release-smoke.test.ts +40
| @@ -202,3 +202,43 @@ test("an empty 200 with no forge or edge headers says so, pointing past the forg | ||
| 202 | 202 | expect(emptyEvidence(new Response(""))).toBe(" (no forge or edge headers)"); |
| 203 | 203 | expect(emptyEvidence(new Response("", { headers: { "x-forge-version": "v1" } }))).toBe(" (x-forge-version: v1)"); |
| 204 | 204 | }); |
| 205 | ||
| 206 | test("an empty body under a content-encoding that is whole unencoded passes: the edge's encoding lost it (#10889)", async () => { | |
Add the companion test: empty under content-encoding even when accept-encoding is identity must still report the empty-body failure. |
||
| 207 | const asked: string[] = []; | |
| 208 | const edge = Bun.serve({ | |
| 209 | port: 0, | |
| 210 | hostname: "127.0.0.1", | |
| 211 | fetch(req) { | |
| 212 | const path = new URL(req.url).pathname; | |
| 213 | const enc = req.headers.get("accept-encoding") ?? ""; | |
| 214 | asked.push(`${path}:${enc}`); | |
| 215 | if (path === "/") return new Response('<link rel="stylesheet" href="/a.css">', { headers: { "content-type": "text/html" } }); | |
| 216 | if (path === "/a.css") return new Response("body{margin:0}", { headers: { "content-type": "text/css" } }); | |
| 217 | if (path === "/sw.js") return new Response("if (r.mode === 'navigate') { e.respondWith(fetch(r)); return; }", { headers: { "content-type": "text/javascript" } }); | |
| 218 | if (path === "/explore" && enc !== "identity") return new Response("", { headers: { "content-encoding": "zstd" } }); | |
| 219 | return new Response("page " + path, { headers: { "x-forge-version": "v1" } }); | |
| 220 | }, | |
| 221 | }); | |
| 222 | const failures = await smoke(`http://127.0.0.1:${edge.port}`, "t", { heavy }); | |
| 223 | expect(failures.filter((f) => f.why.includes("empty body"))).toEqual([]); | |
| 224 | await edge.stop(true); | |
| 225 | expect(asked.some((a) => a === "/explore:identity")).toBe(true); | |
| 226 | }); | |
| 227 | ||
| 228 | test("an empty body under a content-encoding that is empty unencoded too still fails (#10889)", async () => { | |
| 229 | const edge = Bun.serve({ | |
| 230 | port: 0, | |
| 231 | hostname: "127.0.0.1", | |
| 232 | fetch(req) { | |
| 233 | const path = new URL(req.url).pathname; | |
| 234 | if (path === "/") return new Response('<link rel="stylesheet" href="/a.css">', { headers: { "content-type": "text/html" } }); | |
| 235 | if (path === "/a.css") return new Response("body{margin:0}", { headers: { "content-type": "text/css" } }); | |
| 236 | if (path === "/sw.js") return new Response("if (r.mode === 'navigate') { e.respondWith(fetch(r)); return; }", { headers: { "content-type": "text/javascript" } }); | |
| 237 | if (path === "/explore") return new Response(req.headers.get("accept-encoding") === "identity" ? " " : "", { headers: { "content-encoding": "zstd" } }); | |
| 238 | return new Response("page " + path, { headers: { "x-forge-version": "v1" } }); | |
| 239 | }, | |
| 240 | }); | |
| 241 | const failures = await smoke(`http://127.0.0.1:${edge.port}`, "t", { heavy }); | |
| 242 | await edge.stop(true); | |
| 243 | expect(failures.some((f) => f.url.endsWith("/explore") && f.why.includes("empty body"))).toBe(true); | |
| 244 | }); |
This comment belongs to
oncebelow. Put the unencodedBody helper above it.