Skip to content

gates: let the eager-closure budget tolerate a module split without admitting new eager work #2469

Description

@thymikee

Purpose

Decide how the eager-closure gate (scripts/__tests__/eager-closure-budgets.ts) should treat a module split, so that extracting a large module inside a gated closure stops being impossible without weakening the property the gate protects.

Current behavior

Every entry that exists at the merge-base is held to NO GROWTH of its eager import closure, counted in modules. There is no approval path for growth of an existing entry (APPROVED_OVER_CEILING covers first-introduced entries only). A module that is in a gated closure therefore cannot be split: one module becoming N modules is +N−1 for every entry that evaluates it.

Measured on PR #2423 at b21efb0: extracting packages/platform-apple/src/runner/runner-cache-metadata.ts (621 lines) into runner-phase-budget.ts + runner-toolchain-probe.ts grows seven entries by two each: app-lifecycle-facade 120→122, app-resolution-facade 61→63, doctor-facade 101→103, perf-facade 60→62, physical-device-facade 47→49, runner-operations-facade 100→102, runner/index.ts 13→15. The review on #2423 asked for exactly that extraction for ownership clarity, and it had to be deferred.

What the gate protects

The ADR-0019 loading shape: a façade must not evaluate implementation it does not need at import time. The module count is a proxy for eager work. A split does not add eager work when the new modules together evaluate what the old one did, but it does add modules, so the proxy reads it as growth.

Design question

Choose a rule that admits healthy extraction without admitting new eager work. Candidates, none proven:

  1. Count something closer to eager work than module count: total source bytes, or statements at module top level, of the closure. A pure split keeps it flat; a new static edge to real implementation raises it. Costs: a heavier walker; renames and formatting churn move the number.
  2. Recognize a split structurally: the new modules are all new at the head, and each is imported only by modules that were in the old module's importer set at the merge-base. As the reviewer of fix(ios): budget cold toolchain probes for the first-exec signature stall #2423 noted, this does not by itself prove no new eager work (a "new" module can carry a new heavy edge).
  3. An explicit, reviewed approval row for growth of an existing entry (APPROVED_GROWTH), naming the issue, the reason, and the expected delta, retired automatically once the merge-base carries the new shape. Same mechanism as the existing ceiling rows; costs a hand-edited row per split.
  4. Combine 2 and 1: accept a structural split only when the closure's byte or statement total does not grow.

Deliverable

A short design note choosing one, with the walker change and the test that plants (a) a pure split and (b) a split that smuggles a new heavy edge, showing the first passes and the second fails. Then re-run the #2423 extraction as the first consumer.

Non-goals

Loosening no-growth for anything other than splits; changing category ceilings.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions