fix(#4434): win32 unmeasured test files weigh the documented ~2.2x Windows-cost floor, not the Linux-measured mean (#4903)

* fix(#4434): win32 unmeasured test files weigh the documented ~2.2x Windows-cost floor, not the Linux-measured mean

test(#4434): failing-first — win32 unmeasured-file weight must use the
documented Windows-cost multiplier, not the plain table mean

fix(#4434): makeFileWeigher takes a platform and applies
WINDOWS_UNMEASURED_COST_MULTIPLIER (2.2) to the unmeasured-file fallback
on win32 only; measured files and other platforms are unaffected

chore(#4434): changeset fragment

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* test(#4434): add fast-check property coverage for the win32 unmeasured-file weight multiplier

CLAUDE.md requires a fast-check property test for budget-limit logic; the
prior example-based #4434 tests didn't satisfy that. Adds two seeded,
bounded property tests: the unmeasured-file fallback matches the platform
rule for any measured table, and a measured file's weight is
platform-invariant for any measured table.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* chore(#4434): backfill changeset PR number (4903)

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* fix(#4434): the Windows unmeasured-file multiplier must not apply when there is no timings table at all

Real Windows CI on this PR caught a regression my own linux-only gsd-test
verification couldn't see: makeFileWeigher applied
WINDOWS_UNMEASURED_COST_MULTIPLIER even when `timings` is null (missing/
corrupt/empty table), breaking the pre-#2456 "no table degrades to uniform
weight 1" invariant several existing tests depend on. The multiplier now
only applies to a file absent from an otherwise-loaded table — the actual
#4434 mechanism (a real, loaded, Linux-measured table with unmeasured
entries) — never to the no-table-at-all path.

Six pre-existing tests that called makeFileWeigher/pack helpers with no
explicit platform (silently inheriting whatever OS runs them) now pin an
explicit 'linux' platform, since they test the platform-agnostic mean-vs-
median and Object.prototype-safety invariants, not #4434's Windows
behavior. One subprocess-level test is isolated from the real committed
tests/test-timings.json via RUN_TESTS_TIMINGS_FILE, matching this file's
existing isolation convention for cost-profile-sensitive assertions.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

---------

Co-authored-by: sim <sim@local>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Tom Boucher
2026-09-20 20:08:53 -04:00
committed by GitHub
parent ccfed63355
commit 029acd9158
3 changed files with 143 additions and 12 deletions

View File

@@ -535,12 +535,36 @@ function loadTestTimings(timingsPath) {
return { timings, mean, medianWeight: median / mean };
}
// #4434: the table's own `sources` are Linux-only (test-events-linux-node22/24
// .jsonl — never a Windows event stream), and this runner's own chunk-kill
// diagnostic already tells operators "real Windows cost runs ~2.2x the
// recorded figure, so treat every number as a floor" (see the catch block
// around the per-chunk timeout, below). That string was, until this fix,
// advisory text ONLY — nothing in the weigher actually applied it. Verified
// live: `next` @ ccfed6335 (unrelated to the chunk's own diff — see #4434)
// killed Windows conformance shard 3/3 chunk 6/8 at 600016ms; 6 of that
// chunk's 17 files were wholly absent from the table and were packed at the
// table's plain mean, identically to how a Linux/macOS chunk would price
// them. A MEASURED file does not get this multiplier: #4733 already
// calibrates measured-file packing against a real Windows wall-clock via
// MAX_FILES_PER_CHUNK, so inflating measured weights again here would
// double-apply the correction. An unmeasured file has no real data at all —
// it is exactly the "floor, not a verdict" case the diagnostic already warns
// about, so only its fallback gets the multiplier, and only on win32.
const WINDOWS_UNMEASURED_COST_MULTIPLIER = 2.2;
// Build the packer's weight function from a loaded timing table.
//
// A file present in the table weighs its measured duration relative to the
// table mean. A file ABSENT from it weighs 1 — the table MEAN, the same
// value a null table (missing or unparseable file) yields for every file,
// because both states mean the same thing: cost unknown.
// because both states mean the same thing: cost unknown. On win32 the
// fallback is WINDOWS_UNMEASURED_COST_MULTIPLIER instead of 1 (see #4434,
// above) — but ONLY for a file absent from an otherwise-loaded table; a
// completely missing/corrupt/empty timings file (`timings` is `null`) still
// degrades to uniform weight 1 on every platform, matching the pre-#2456
// count-based-packing invariant many existing tests depend on. Every other
// platform keeps the plain mean.
//
// This was previously `timings.medianWeight`, on the claim that an absent
// file "costs chunk balance, never a red build." That claim is false. In a
@@ -550,11 +574,14 @@ function loadTestTimings(timingsPath) {
// cause a red build: Windows conformance shard 2/3, chunk 4/6 was killed at
// 600018ms with ZERO failing tests, because files absent from the table
// packed as if they were nearly free and the chunk blew the 600s cap.
// Empirically, mean is the right estimate for an unknown file: 9 unmeasured
// files that caused the incident averaged 6659ms against a table mean of
// 7152ms — within 7%.
function makeFileWeigher(timings) {
// Empirically, mean is the right estimate for an unknown file on the
// platform the table was MEASURED on: 9 unmeasured files that caused the
// incident averaged 6659ms against a table mean of 7152ms — within 7%. That
// equivalence does not hold on win32, where the table's own sources are
// Linux-only (#4434).
function makeFileWeigher(timings, platform = process.platform) {
if (!timings) return () => 1;
const unmeasuredWeight = platform === 'win32' ? WINDOWS_UNMEASURED_COST_MULTIPLIER : 1;
return (f) => {
const key = basename(f);
// Own-property check before the lookup. This is defense-in-depth, NOT a
@@ -567,7 +594,9 @@ function makeFileWeigher(timings) {
// keeps the lookup correct for arbitrary input, since this function is
// exported and does not control its caller's strings.
const ms = Object.hasOwn(timings.timings, key) ? timings.timings[key] : undefined;
return typeof ms === 'number' && Number.isFinite(ms) && ms >= 0 ? ms / timings.mean : 1;
return typeof ms === 'number' && Number.isFinite(ms) && ms >= 0
? ms / timings.mean
: unmeasuredWeight;
};
}
@@ -1192,7 +1221,7 @@ function main() {
let weigherMemo = null;
const fileWeightOf = () => {
if (weigherMemo === null) {
weigherMemo = makeFileWeigher(loadedTimings());
weigherMemo = makeFileWeigher(loadedTimings(), process.platform);
}
return weigherMemo;
};
@@ -1854,6 +1883,7 @@ module.exports = {
defaultMaxFilesPerChunk,
loadTestTimings,
makeFileWeigher,
WINDOWS_UNMEASURED_COST_MULTIPLIER,
makeMeasuredPredicate,
packChunks,
// 2026-09-07 (PR #4497): the codex-config.test.cjs chunk-isolation fix —