Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions apps/e2e-test/e2e/multipart-crash-restore-regressions.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)}`
Expand Down
7 changes: 6 additions & 1 deletion apps/e2e-test/e2e/multipart-cross-reload-resume.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions apps/e2e-test/e2e/multipart-resume-harness.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
25 changes: 23 additions & 2 deletions apps/e2e-test/landing/ingestion-verification.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<unknown[][] | null> {
let res: Response
Expand All @@ -43,7 +53,10 @@ async function runHogql(query: string): Promise<unknown[][] | null> {
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
Expand All @@ -58,7 +71,15 @@ async function runHogql(query: string): Promise<unknown[][] | null> {
`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 ?? []
}
Expand Down
4 changes: 2 additions & 2 deletions apps/e2e-test/src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -163,7 +163,7 @@ function App() {
) : (
<MultipartResumeDemo
serverUrl={
params.get('server') ?? 'http://localhost:53061'
params.get('server') ?? 'http://localhost:31061'
}
maxFiles={Number(params.get('maxFiles') ?? 1) || 1}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -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 {
Expand Down
12 changes: 10 additions & 2 deletions scripts/package-smoke-consumer.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down Expand Up @@ -396,7 +404,7 @@ export default defineConfig({
},
build: {
manifest: true,
chunkSizeWarningLimit: 1600,
chunkSizeWarningLimit: 2100,
rollupOptions: {
output: {
manualChunks(id) {
Expand Down
Loading