fix(release): derive the Sentry-injected artifact ceiling and gate the packlist before tagging

Merged
UN
6d ago
+984-52

Why

Tag v2026.11.5 exists but was never published. Both Release runs, the tag push 41980508196 and the scheduled recovery 42005294363, passed the base packlist profile at 19,979,108 of 20,000,000 bytes. Both then failed Sentry-injected package artifact gate with unpacked_size:20257274>20250000. The blocker issue is #1668.

sentry-cli sourcemaps inject dist/ adds exactly 358 bytes to every emitted dist/**/*.js file: 777 files and 278,166 bytes, the same locally and in CI. The fixed 250,000-byte allowance covers only about 698 files. Once the emitted module count grew past that, the allowance no longer covered the transform.

No step before the tag could see this:

  • CI runs only the base profile.
  • The pre-tag preflight in run-gates.mjs never ran the artifact gate, although docs/RELEASING.md said its packaging phase did.

So the first injected check ran on an immutable tag.

What changes

  • Derived injected ceiling. package-artifact-budget.json v4 defines sentry-injected as an injection profile: the base ceiling plus max_bytes_per_injected_file (384) for each packed file matching dist/ + .js. Base content that fits the base ceiling also fits after injection.
    • The base ceiling (20,000,000 bytes) and the file-count ceiling (1,800) are unchanged.
    • The gate validates the declaration fail-closed: an unknown base profile, an empty prefix or suffix, a non-positive or non-integer allowance, or a null declaration is refused.
    • The receipt adds an injection block: base_profile, injected_file_count, max_bytes_per_injected_file and injection_allowance.
  • Pre-tag gate. gate-registry.json local_preflight gains a package-artifact step after the build, with skips forbidden. An over-budget candidate is refused before any version commit or tag. run-gates.spec.ts asserts that the step runs after the build.
  • Allowance bound to the real binary. sentry-injection-allowance.spec.ts runs the installed @sentry/cli 3.8.2 offline against plain, shebang and large modules. It asserts that the added bytes are positive and within the allowance, and that sentry:inject targets the declared prefix. A sentry-cli upgrade that grows the snippet fails in its own PR.
  • docs/RELEASING.md describes the pre-tag step and the derived profile.
  • Tracker. pm-998juj records the evidence. New items pm-gh1670 (#1670) and pm-gh1671 (#1671). This branch also carries #1669: the pm-998juj claim release, and pm-gh1662 to pm-gh1667 for #1662 to #1667. #1669 is superseded by this PR.

Evidence

  • Replaying the exact CI packlist against v4: base 19,979,108 ≤ 20,000,000 and injected 20,257,274 ≤ 20,298,368 (777 × 384).
  • Current tree base gate: 19,979,982 bytes, 1,799 files, ok.
  • Full suite: 12,064 passed. The one local failure is the known Node 26 npm/package.json resolution in composed-continuation.integration.spec.ts, which passes with NODE_PATH set and in CI.
  • package-artifact-gate.mjs coverage is 100/100/100/100. eslint, jscpd, docs-skills, secret scan, gate-registry validation and changelog:pm:check (changed: false) all pass.

After merge

Exact-tag recovery copies the gate and budget from dispatch-time main into runner-temp release controls, then checks out the unpublished tag. A release.yml dispatch for v2026.11.5 then publishes the unchanged tagged bytes under this policy.

Headroom remains tight: 20,018 bytes and one file. The next pm-998juj weight-reduction tranche is needed before features that add emitted modules.

Summary by Sourcery

Gate package size, including Sentry-injected output, before release tags are created.

Bug Fixes:

  • Prevent releases from creating immutable tags when the npm package would exceed its artifact budget after Sentry debug-ID injection.

Enhancements:

  • Derive the Sentry-injected artifact ceiling from the base budget and the number of packed JavaScript files, with fail-closed profile validation and injection details in gate receipts.
  • Add pre-tag base and simulated Sentry-injected artifact checks, measuring the installed Sentry CLI on a disposable copy before versioning or tagging.
  • Bind the injection allowance to the installed Sentry CLI behavior across representative emitted module shapes.

Documentation:

  • Document the pre-tag artifact checks, derived injection budget, and release packaging behavior.

Tests:

  • Add coverage for derived budget validation, disposable-copy injection simulation, gate ordering, and the installed Sentry CLI's injection allowance.

Chores:

  • Record related release-budget tracking issues and task history.

Summary by cubic

Fixes release builds failing the Sentry-injected artifact gate after the tag already exists, which left v2026.11.5 cut but unpublished. sentry-cli sourcemaps inject dist/ adds 358 bytes to every emitted dist/**/*.js file, and the fixed 250,000-byte allowance stopped covering the transform once 777 files pushed the injected size past it; no pre-tag step ran the artifact gate.

Budget v4 derives the injected ceiling: the base ceiling plus 384 bytes per packed dist/**/*.js file, so anything that fits the base profile also fits after injection. local_preflight now runs the base profile and then the simulated sentry-injected profile after the build, with skips forbidden. --simulate-injection copies dist/ to a temp directory, runs the checkout's installed sentry-cli on the copy, adds each packed file's measured growth to the packlist, and validates the injection profile before any version commit or tag. It discards sentry-cli's per-file stdout (which can exhaust the command's buffer as the module count grows), keeps stderr for diagnostics, and times out after five minutes. Injecting the checkout in place would be unsafe: tsc builds incrementally, so a later build could keep the injected modules and fail the next base check by about 278 KB.

  • The gate validates injection declarations fail-closed; the receipt records the simulated source and the added bytes.
  • A new spec runs the installed @sentry/cli 3.8.2 against plain, shebang, and large modules, so a sentry-cli upgrade that grows the snippet fails in its own PR.
  • After this lands, a release.yml dispatch for v2026.11.5 publishes the unchanged tagged bytes.
  • The base ceiling and file-count ceiling are unchanged; headroom is about 19.7 KB and one file.