From f136c733c5c90e395c39897a8a753f26a14b8da4 Mon Sep 17 00:00:00 2001 From: Hassan Abdel-Rahman Date: Thu, 23 Jul 2026 20:16:06 -0400 Subject: [PATCH 1/2] Size memory-baseline hard gate to each module's observed swing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The per-module post-GC boundary delta is dominated by non-deterministic settle-GC drain timing, so heavy modules swing run-to-run by >100MB with no leak. When a module's entire recent window is negative, its mean and ceiling both floor to zero and the hard gate collapses to "any single reading over +50MB fails" — so a module that routinely swings from -120MB to -8MB gets blocked the one run it happens to land at +54MB. Fold the recent window's peak-to-peak spread into the hard threshold: a run must clear the ceiling by more than the module has already shown it can move on its own before it blocks. Tight-window modules (spread below the absolute threshold) are unaffected; a genuine step past the observed swing still fails. The soft, mean-anchored warning is untouched, so upward trends still surface. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../host/scripts/check-memory-baseline.mjs | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/packages/host/scripts/check-memory-baseline.mjs b/packages/host/scripts/check-memory-baseline.mjs index 50fa04f13df..7bbcfa80ef4 100644 --- a/packages/host/scripts/check-memory-baseline.mjs +++ b/packages/host/scripts/check-memory-baseline.mjs @@ -72,6 +72,24 @@ const baselineCeiling = (entry) => { return entry?.delta_mb; }; +// The peak-to-peak spread of the recent window (max sample − min sample). This +// is the module's own demonstrated run-to-run noise: a module whose post-GC +// boundary delta swings because the settle-GC drains a large transient on some +// runs but not others will show a wide spread even with no leak. The hard gate +// folds this spread into its threshold so a value inside the module's already- +// exhibited swing can't block the build — an all-negative window like +// [-73, -74, -8, -19, -120] spans 112MB, so a one-off +54 reading is noise, not +// a regression, even though it clears the (floored-at-zero) ceiling by >50MB. +// Modules with a tight window (spread < the absolute hard threshold) are +// unaffected. Zero for a single-value baseline that carries no sample window, +// leaving the absolute/relative thresholds to govern on their own. +const baselineSpread = (entry) => { + if (Array.isArray(entry?.samples) && entry.samples.length > 1) { + return Math.max(...entry.samples) - Math.min(...entry.samples); + } + return 0; +}; + const fmtSamples = (entry) => Array.isArray(entry?.samples) && entry.samples.length > 0 ? `[${entry.samples.map((s) => s.toFixed(1)).join(', ')}]` @@ -115,9 +133,19 @@ for (const [mod, data] of Object.entries(current)) { SOFT_ABSOLUTE_MB, effectiveBase * SOFT_RELATIVE, ); + // The hard threshold also absorbs the module's demonstrated peak-to-peak + // noise, so a run must clear the ceiling by more than the module has already + // shown it can move on its own before it blocks. This is what keeps a module + // whose recent window is entirely negative honest: its baseline and ceiling + // both floor to zero, so the absolute gate alone would fail any single + // positive reading — including one from a module that routinely swings by + // >100MB and merely landed positive this run. Sizing the threshold to the + // observed swing lets that reading through while a genuine step past the + // swing still blocks. const hardThreshold = Math.max( HARD_ABSOLUTE_MB, effectiveBase * HARD_RELATIVE, + baselineSpread(base), ); const pct = From 7d75ab6d2d0d319a13885c6c91f9e635b0679e70 Mon Sep 17 00:00:00 2001 From: Hassan Abdel-Rahman Date: Thu, 23 Jul 2026 20:33:54 -0400 Subject: [PATCH 2/2] Make the hard-gate comments and report label match the ceiling+swing math MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The file header and the failures-table heading still described the gate as a flat ">2x baseline or +50MB" rule, which stopped being accurate once the hard gate moved to a ceiling-relative measurement. State the actual condition — a delta clears the recent ceiling by more than the larger of +50MB, 100% of the baseline, or the module's own observed swing — and correct the inline note that implied the absolute term blocks any positive reading rather than one over 50MB. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../host/scripts/check-memory-baseline.mjs | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/packages/host/scripts/check-memory-baseline.mjs b/packages/host/scripts/check-memory-baseline.mjs index 7bbcfa80ef4..236ed594a36 100644 --- a/packages/host/scripts/check-memory-baseline.mjs +++ b/packages/host/scripts/check-memory-baseline.mjs @@ -1,7 +1,9 @@ #!/usr/bin/env node // Compares per-module memory deltas from a CI run against a committed baseline. -// Exits 0 on pass/warn, exits 1 on hard failure (>2x baseline or +50MB absolute). +// Exits 0 on pass/warn, exits 1 on hard failure — when a module's delta clears +// its recent ceiling by more than the larger of +50MB, 100% of the baseline, or +// the module's own observed run-to-run swing. // // Usage: node check-memory-baseline.mjs // @@ -134,14 +136,13 @@ for (const [mod, data] of Object.entries(current)) { effectiveBase * SOFT_RELATIVE, ); // The hard threshold also absorbs the module's demonstrated peak-to-peak - // noise, so a run must clear the ceiling by more than the module has already - // shown it can move on its own before it blocks. This is what keeps a module - // whose recent window is entirely negative honest: its baseline and ceiling - // both floor to zero, so the absolute gate alone would fail any single - // positive reading — including one from a module that routinely swings by - // >100MB and merely landed positive this run. Sizing the threshold to the - // observed swing lets that reading through while a genuine step past the - // swing still blocks. + // noise. When a module's recent window is entirely negative, its mean baseline + // and its ceiling both floor to zero, so the +50MB absolute term alone would + // block any single reading over 50MB — even from a module that routinely + // swings by >100MB and merely landed positive this run. Sizing the threshold + // to the observed swing means a run has to clear the recent ceiling by more + // than the module's own run-to-run range before it blocks; tighter-window + // modules fall back to the absolute/relative terms. const hardThreshold = Math.max( HARD_ABSOLUTE_MB, effectiveBase * HARD_RELATIVE, @@ -195,7 +196,7 @@ const baselineHeader = if (failures.length > 0) { lines.push( - `### Failures (>${HARD_RELATIVE * 100}% increase or +${HARD_ABSOLUTE_MB}MB)\n`, + `### Failures (over recent ceiling by >${HARD_ABSOLUTE_MB}MB, >${HARD_RELATIVE * 100}%, or the observed swing)\n`, ); lines.push( `| Module | ${baselineHeader} | Current | Change | Recent samples |`,