@@ -2792,7 +2792,7 @@ function countTscErrors(output, { dropRootDirDiagnostics = false } = {}) {
27922792// same tree. ⚠️ The asymmetry IS the defect: a local pass was never a claim
27932793// about CI, and nothing said so out loud.
27942794//
2795- // ## Where the number comes from -- CI, never this box
2795+ // ## Where the runner's DEFAULT old space comes from -- CI, never this box
27962796//
27972797// Read off the CI runner itself: run 33136681083, job `Type Check · debt
27982798// ledger`, at 6d097a604, Node v22.23.2. The `packages/qa/http-conformance`
@@ -2851,36 +2851,66 @@ function countTscErrors(output, { dropRootDirDiagnostics = false } = {}) {
28512851//
28522852// The scarce resource is therefore NOT the runner's memory -- 15.6 GiB with
28532853// ~918 MB of it spoken for -- but V8's DEFAULT old space on that runner, which
2854- // is 4096 MB. This constant describes that default, and as of 2026-09-03 it
2855- // still describes it exactly. That is why the re-measure leaves it here.
2854+ // the reading above pins at 4096 MB from two directions.
28562855//
2857- // ⚠️ If 4096 is wrong, it is wrong DOWNWARD -- the only safe direction. This
2858- // number's entire job is to be no HIGHER than CI's ceiling. A pin ABOVE CI's is
2859- // worse than no pin at all: it makes local runs pass where CI still OOMs, which
2860- // is exactly this defect with extra confidence attached. The 2026-09-03
2861- // reading above is the first taken with the runner in hand rather than
2862- // inferred from a crash, and it lands on the same 4096 from the other side.
2856+ // ## The raise, on that measurement (#14569, ruled A then A1, 2026-09-03)
2857+ //
2858+ // A default is not a budget. The ledger's heaviest program was paying GC
2859+ // pressure to fit inside 4096 rather than fitting, and the tripwire (spec
2860+ // declaration growth) is a weekly event, so the ruling raises the ceiling --
2861+ // on the measurement above, never on a typed number. What that raise is NOT
2862+ // is a bigger promise about the box. It is the pair below, and ⛔ neither
2863+ // half is shippable alone:
2864+ //
2865+ // the workflow `.github/workflows/lint.yml`, job `typecheck-debt`, step
2866+ // "Re-measure the type-check DEBT / TEST_DEBT ledger", now
2867+ // runs under `NODE_OPTIONS: --max-old-space-size=6144`. That
2868+ // is the half that actually hands the process the old space:
2869+ // V8's default there is 4096 and no constant in this file can
2870+ // move it.
2871+ // this constant 6144 -- a description of the old space that step now
2872+ // really has, exactly as 4096 described the default before it.
2873+ //
2874+ // ⛔ Raising this constant ALONE cannot buy the ledger a roomier run --
2875+ // measured on 2026-09-03, not reasoned. `remeasureHeapCeiling` below takes the
2876+ // MINIMUM of this pin and the limit the running process actually has, so with
2877+ // the pin at 6144 and the gate started under the runner's DEFAULT the chosen
2878+ // ceiling is still 4144 -- and the `stale` arm below then refuses the run
2879+ // outright: `--re-measure` exits 1 before the first tsc ("the pin is now ABOVE
2880+ // the ceiling it claims to describe"), on every PR and on `main`. That
2881+ // refusal is the pairing's enforcement -- it is what caught the bare raise
2882+ // when it was attempted -- and both directions are pinned as self-test rows
2883+ // below ("the runner as the workflow now starts it" and "the same runner
2884+ // WITHOUT it"). Delete the `NODE_OPTIONS` line and the lane says so, loudly,
2885+ // on the runner.
2886+ //
2887+ // ⚠️ If 6144 is wrong, it is wrong DOWNWARD -- the only safe direction. This
2888+ // number's entire job is to be no HIGHER than the ceiling the process running
2889+ // tsc on CI really has. A pin ABOVE it is worse than no pin at all: it makes
2890+ // local runs pass where CI still OOMs, which is exactly this defect with extra
2891+ // confidence attached.
28632892//
28642893// ⛔ Do not raise this to make a local measurement complete. `--re-measure`
28652894// OOMing under this ceiling is the gate WORKING -- it is CI's failure,
28662895// reproduced on your box before you push. What grew is the type graph, not the
28672896// memory CI has. (`packages/spec/tsup.config.ts` carries the other half of this
28682897// lesson from the build side: a ceiling above the box's real memory does not
28692898// buy a bigger run, it converts a recoverable heap error into an exit-137
2870- // SIGKILL that carries no diagnostic at all.)
2871- //
2872- // ⛔ And raising it ALONE cannot buy the ledger a roomier run -- measured on
2873- // 2026-09-03, not reasoned. `remeasureHeapCeiling` below takes the MINIMUM of
2874- // this pin and the limit the running process actually has, so with the pin at
2875- // 6144 and the gate started under the runner's own default the chosen ceiling
2876- // is still 4144 -- and the `stale` arm below then refuses the run outright:
2877- // `--re-measure` exits 1 before the first tsc ("the pin is now ABOVE the
2878- // ceiling it claims to describe"), reproduced against a 4144 MB process. A
2879- // raise has to hand the gate PROCESS the memory first -- a `NODE_OPTIONS` on
2880- // the job's re-measure step -- so the pin keeps describing what the process
2881- // really has. That is a workflow decision, not one this constant can take on
2882- // its own; #14569 carries it.
2883- const CI_TSC_HEAP_CEILING_MB = 4096 ;
2899+ // SIGKILL that carries no diagnostic at all.) The 6144 is not an exception to
2900+ // that rule, it is an application of it: the runner was MEASURED to carry the
2901+ // heaviest program under a 6144 cap (4,420,706K used, 4,545,500 kB peak RSS,
2902+ // 10,562,192 kB still available at the tightest moment) before it was pinned.
2903+ //
2904+ // ⚠️ One protection the pair costs, recorded here so nobody rediscovers it as
2905+ // a surprise. With `NODE_OPTIONS` set explicitly on that step,
2906+ // `heap_size_limit` there reads 6192 whatever the runner's physical memory
2907+ // does -- so on THAT job the `stale` arm can no longer notice the runner
2908+ // shrinking; it now only notices a pin above a DEFAULTED process. The margin
2909+ // is what makes that acceptable: the pin asks for 6144 MB where the
2910+ // measurement found 10,562,192 kB available at the heaviest moment, ~1.7x. If
2911+ // that margin is ever in doubt the answer is a fresh runner measurement and a
2912+ // smaller number in BOTH places, ⛔ never a bigger one here.
2913+ const CI_TSC_HEAP_CEILING_MB = 6144 ;
28842914
28852915/**
28862916 * The last `--max-old-space-size` in a `NODE_OPTIONS` string, in MB, or null.
@@ -5225,15 +5255,44 @@ function selfTest() {
52255255 expect : { mb : CI_TSC_HEAP_CEILING_MB , stale : false } ,
52265256 } ,
52275257 {
5228- // `+ 48` is the RUNNER, not a construction: the `Type Check · debt
5229- // ledger` job reports a `heap_size_limit` of 4144 MB for its 4096 MB old
5230- // space (measured there 2026-09-03, #14569), so this row is the shape of
5231- // the machine whose verdict the pin exists to describe -- and the row
5232- // above it is every box that is roomier than that one.
5258+ // `+ 48` is the RUNNER's offset, not a construction: V8 reports the old
5259+ // space plus a fixed ~48 MB of other spaces, measured on the `Type Check
5260+ // · debt ledger` job itself (4144 for a 4096 old space, 2026-09-03,
5261+ // #14569). So this row is the machine whose limit EQUALS the pin with no
5262+ // caller flag in play -- and the row above it is every box roomier than
5263+ // that one.
52335264 label : 'on a box shaped like CI the ceiling is a no-op that still names itself' ,
52345265 where : { heapLimitMb : CI_TSC_HEAP_CEILING_MB + 48 , onCi : true } ,
52355266 expect : { mb : CI_TSC_HEAP_CEILING_MB , stale : false } ,
52365267 } ,
5268+ {
5269+ // THE RUNNER AS THE WORKFLOW NOW STARTS IT (#14569). `lint.yml`'s
5270+ // re-measure step sets `NODE_OPTIONS: --max-old-space-size=6144`, so the
5271+ // gate process reports 6192 AND carries a caller cap EQUAL to the pin.
5272+ // Both candidates tie, the tie-break keeps the CI ceiling's name, and
5273+ // that name is what the job's log then prints. Pinned because an
5274+ // off-by-one in either direction here reads as a caller cap overriding
5275+ // the pin on the one machine whose verdict counts.
5276+ label : "the workflow's own NODE_OPTIONS ties the pin and is not read as a tighter caller cap" ,
5277+ where : {
5278+ heapLimitMb : CI_TSC_HEAP_CEILING_MB + 48 ,
5279+ nodeOptions : `--max-old-space-size=${ CI_TSC_HEAP_CEILING_MB } ` ,
5280+ onCi : true ,
5281+ } ,
5282+ expect : { mb : CI_TSC_HEAP_CEILING_MB , stale : false } ,
5283+ } ,
5284+ {
5285+ // THE OTHER HALF OF THE PAIR, and the row that keeps the two halves
5286+ // inseparable. 4144 is the runner's DEFAULT `heap_size_limit`, measured
5287+ // on that job 2026-09-03. Take the `NODE_OPTIONS` line back out of
5288+ // `lint.yml` and this is the reading the gate gets: refused outright,
5289+ // before the first tsc, on every PR and on `main`. A bare raise of the
5290+ // constant was attempted and this is what caught it, so the pin above
5291+ // cannot quietly outlive the workflow line that pays for it.
5292+ label : 'the same runner WITHOUT the workflow NODE_OPTIONS -- its 4144 MB default -- is refused' ,
5293+ where : { heapLimitMb : 4144 , onCi : true } ,
5294+ expect : { mb : 4144 , stale : true } ,
5295+ } ,
52375296 {
52385297 // Never RAISE. Promising V8 memory the box does not have trades a
52395298 // recoverable heap error for a kernel SIGKILL that says nothing.
0 commit comments