diff --git a/apps/e2e-test/e2e/multipart-crash-restore-regressions.spec.ts b/apps/e2e-test/e2e/multipart-crash-restore-regressions.spec.ts index 2ce93d5b..01a888eb 100644 --- a/apps/e2e-test/e2e/multipart-crash-restore-regressions.spec.ts +++ b/apps/e2e-test/e2e/multipart-crash-restore-regressions.spec.ts @@ -52,10 +52,12 @@ import { announceMinioSkip('multipart-crash-restore-e2e') -// Its own port: the sibling spec owns 53061, and two spec files must never -// contend for one harness even when Playwright raises the worker count. +// Its own port: the sibling spec owns 31061, and two spec files must never +// contend for one harness even when Playwright raises the worker count. Both +// sit below 32768 deliberately — see that spec for why the old 5306x pair was +// losing races against the runner's own ephemeral source ports. const HARNESS_PORT = Number( - process.env.UPUP_E2E_CRASH_RESTORE_SERVER_PORT ?? 53062, + process.env.UPUP_E2E_CRASH_RESTORE_SERVER_PORT ?? 31062, ) const HARNESS_URL = `http://localhost:${HARNESS_PORT}` const SCENARIO = `/?scenario=multipart-resume&server=${encodeURIComponent(HARNESS_URL)}` diff --git a/apps/e2e-test/e2e/multipart-cross-reload-resume.spec.ts b/apps/e2e-test/e2e/multipart-cross-reload-resume.spec.ts index 8c03c58f..d9eebb44 100644 --- a/apps/e2e-test/e2e/multipart-cross-reload-resume.spec.ts +++ b/apps/e2e-test/e2e/multipart-cross-reload-resume.spec.ts @@ -61,7 +61,12 @@ const SOURCE_BYTES = minioReady : Buffer.alloc(0) const SOURCE_SHA256 = sha256(SOURCE_BYTES) -const HARNESS_PORT = Number(process.env.UPUP_E2E_RESUME_SERVER_PORT ?? 53061) +// 31061, not 53061: Linux hands out ephemeral source ports from 32768-60999, +// so a fixed LISTEN port inside that window can be transiently held by one of +// the runner's own outbound connections. That is exactly how the 2026-09-06 +// nightly died — `EADDRINUSE :::53062` on the sibling spec, then 60 s of +// polling a harness that never bound. Below 32768 the kernel never competes. +const HARNESS_PORT = Number(process.env.UPUP_E2E_RESUME_SERVER_PORT ?? 31061) const HARNESS_URL = `http://localhost:${HARNESS_PORT}` let harness: PresignHarness | null = null diff --git a/apps/e2e-test/e2e/multipart-resume-harness.ts b/apps/e2e-test/e2e/multipart-resume-harness.ts index e3475378..fb6dedbb 100644 --- a/apps/e2e-test/e2e/multipart-resume-harness.ts +++ b/apps/e2e-test/e2e/multipart-resume-harness.ts @@ -101,6 +101,10 @@ export interface PresignHarness { * Deliberately NOT :53060 — the cross-framework gate's harness owns that port * and allows only the six storybook origins, so reusing it would 403 the app. * Each real-MinIO spec file owns its own port so two files can never contend. + * Pick that port BELOW 32768: Linux draws ephemeral source ports from + * 32768-60999, so a fixed listen port in that window loses races against the + * runner's own outbound connections (`EADDRINUSE`, then a 60 s poll against a + * harness that never bound — the 2026-09-06 nightly). */ export async function startPresignHarness( port: number, diff --git a/apps/e2e-test/landing/ingestion-verification.spec.ts b/apps/e2e-test/landing/ingestion-verification.spec.ts index e25ec98e..5a6360ea 100644 --- a/apps/e2e-test/landing/ingestion-verification.spec.ts +++ b/apps/e2e-test/landing/ingestion-verification.spec.ts @@ -33,6 +33,16 @@ if (!queryKey) { * (array-of-arrays, column order = SELECT order). An auth/bad-request failure * throws (RED — an invalid key must never look like a pass); a transient 5xx / * network error returns null so the caller's poll can retry. + * + * `refresh: 'force_blocking'` is load-bearing. The Query API's default mode + * ("blocking") serves a CACHED result whenever one exists for the identical + * query text and is not yet stale — and every iteration of a poll below sends + * byte-identical text. So a first poll that runs before the events are visible + * caches an empty result, and the remaining 90 s replay that cache. That is + * exactly how the nightly "AI thumbs events landed" case failed 8 nights out + * of 9 (2026-08-26 → 09-07): PostHog's own `created_at` shows both events + * ingested 1-2 s after capture, i.e. present for the whole poll — the poll just + * kept reading the stale cache. `force_blocking` recalculates on every call. */ async function runHogql(query: string): Promise { let res: Response @@ -43,7 +53,10 @@ async function runHogql(query: string): Promise { authorization: `Bearer ${queryKey}`, 'content-type': 'application/json', }, - body: JSON.stringify({ query: { kind: 'HogQLQuery', query } }), + body: JSON.stringify({ + query: { kind: 'HogQLQuery', query }, + refresh: 'force_blocking', + }), }) } catch { return null // network blip — let the poll retry @@ -58,7 +71,15 @@ async function runHogql(query: string): Promise { `PostHog Query API rejected the query (HTTP 400): ${await res.text()}`, ) } - if (!res.ok) return null // transient upstream error — retry + if (!res.ok) { + // Transient upstream error — retry, but leave a trace in the job log + // so a poll that times out is never a silent mystery. + // eslint-disable-next-line no-console + console.log( + `[ingestion] PostHog Query API HTTP ${res.status} — retrying`, + ) + return null + } const json = (await res.json()) as { results?: unknown[][] } return json.results ?? [] } diff --git a/apps/e2e-test/src/main.tsx b/apps/e2e-test/src/main.tsx index 6247aaf3..283fc9a0 100644 --- a/apps/e2e-test/src/main.tsx +++ b/apps/e2e-test/src/main.tsx @@ -97,7 +97,7 @@ function RestrictionsDemo() { /** * Server-mode multipart scenario — loaded via - * `/?scenario=multipart-resume&server=http://localhost:53061`. + * `/?scenario=multipart-resume&server=http://localhost:31061`. * * The only story in this app that talks to a REAL @useupup/server + MinIO: * multipart at a 5 MiB threshold/part size with crash recovery on, which is @@ -163,7 +163,7 @@ function App() { ) : ( diff --git a/packages/server/tests/integration/drive-clients-live.integration.test.ts b/packages/server/tests/integration/drive-clients-live.integration.test.ts index 1e9a2564..a6fb9aa3 100644 --- a/packages/server/tests/integration/drive-clients-live.integration.test.ts +++ b/packages/server/tests/integration/drive-clients-live.integration.test.ts @@ -13,7 +13,7 @@ // configured but whose token is broken does NOT skip — minting happens in // beforeAll, so a bad token throws there and the suite goes RED (configured but // broken is a real signal, never silently swallowed). -import { describe, it, expect, beforeAll } from 'vitest' +import { describe, it, expect, beforeAll, vi } from 'vitest' import { readFileSync } from 'node:fs' import { fileURLToPath } from 'node:url' import { dirname, join } from 'node:path' @@ -32,6 +32,18 @@ type ProviderSlug = (typeof PROVIDERS)[number] const SANDBOX_ON = process.env.UPUP_DRIVE_SANDBOX === '1' +// Every test here is network-bound against a PRODUCTION cloud API, and the +// download case costs a folder listing plus a redirect-followed fetch and a full +// stream drain per fixture. Vitest's 5 s default is a unit-test budget nobody +// chose for this suite: under it the one-drive download test timed out on the +// 2026-08-26, 09-02, 09-04, 09-05 and 09-06 nightlies while the other 24 +// assertions passed every time — a red gate reporting the clock, not the code. +// What this suite gates is byte-integrity (sha256 vs the committed fixture) and +// never latency, so give it a ceiling a real network deserves; a provider that +// is genuinely down or broken still goes RED, just later. hookTimeout covers +// beforeAll's OAuth token mint, which is the same kind of round trip. +vi.setConfig({ testTimeout: 30_000, hookTimeout: 30_000 }) + // ── Env plumbing ───────────────────────────────────────────────────────── function env(name: string): string | undefined { diff --git a/scripts/package-smoke-consumer.mjs b/scripts/package-smoke-consumer.mjs index 4edde1da..ffff6546 100644 --- a/scripts/package-smoke-consumer.mjs +++ b/scripts/package-smoke-consumer.mjs @@ -38,7 +38,15 @@ const kib = 1024 // (+~30) plus margin; anything larger must be a deliberate re-base, not a // bump-to-green. Whether locales belong on the mandatory path is deferred (F-701). const consumerEntryChunkBudget = 560 * kib -const consumerOptionalChunkBudget = 1600 * kib +// The optional (opt-in HEIC) chunk is libheif-js's emscripten bundle — upstream +// WASM we do not author and cannot shrink. Re-based 1600→2100 KiB 2026-09-09: +// libheif-js 1.23.2 (published 2026-09-05) grew it from ~1425 KiB to 1939.4 KiB, +// and the consumer installs fresh from core's `^1.19.8` range with no lockfile +// (a real consumer gets the same resolution), so the nightly went red the very +// next run with no repo change. This budget guards the OPT-IN path only — +// core's mandatory path is guarded by `consumerEntryChunkBudget` above and by +// `.size-limit.json`. Keep the vite `chunkSizeWarningLimit` below in step. +const consumerOptionalChunkBudget = 2100 * kib function assertInsideRepo(target) { const rel = relative(repoRoot, resolve(target)) @@ -396,7 +404,7 @@ export default defineConfig({ }, build: { manifest: true, - chunkSizeWarningLimit: 1600, + chunkSizeWarningLimit: 2100, rollupOptions: { output: { manualChunks(id) {