Skip to content

fix: restore the root bundle and enforce a budget (0.16.0) - #24

Merged
Gdewilde merged 3 commits into
mainfrom
fix/root-bundle-regression
Aug 2, 2026
Merged

fix: restore the root bundle and enforce a budget (0.16.0)#24
Gdewilde merged 3 commits into
mainfrom
fix/root-bundle-regression

Conversation

@Gdewilde

@Gdewilde Gdewilde commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

0.15.0 shipped a bundle regression. This fixes it and adds a check so it cannot recur silently.

The regression

#21 cut the root entry from 27.7 kB to 9.6 kB. Over the following commits payments, gateway, entitlement and firewall were each exported from the package root, and it climbed back to 22.5 kB — which is what 0.15.0 published.

The root entry ships in every consumer's edge middleware and runs on every request. Sites that never charge anyone are currently paying for a firewall recommender that can never execute there.

dist/index.js     22,497 → 11,608 B   (8,695 → 4,546 gzipped)   −48%

Entry points

Import Contains
@apideck/agent-analytics detection, classification, agentPolicy, trackVisit
…/verify Web Bot Auth + published IP range tables
…/payments 402 challenges, gateways, entitlements
…/firewall WAF recommendations (offline tool)
…/markdown Markdown-twin negotiation

agentPolicy stays in the root deliberately — classification with no policy to apply it to is half a library, and it costs ~2 kB. That's why the root lands at 11.6 kB rather than back at 9.6 kB.

Breaking for anyone importing paymentGate, entitlementGateway, paymentRequired or recommendFirewallRules from the root. 0.15.0 published hours ago, so realistically nobody. Every example in the README, the testing guide and the site has been updated.

The check

npm run size measures each entry gzipped against size-budget.json and fails the build over budget. Zero dependencies — node:zlib does the work.

entry               gzipped     budget  used
dist/index.js       4.44 kB    4.88 kB   91%
dist/verify.js      6.25 kB    7.42 kB   84%
dist/pay.js         4.05 kB    4.49 kB   90%
dist/firewall.js    2.80 kB    3.13 kB   90%
dist/markdown.js    1.03 kB    1.27 kB   81%

Verified by reintroducing the exact regression — re-exporting pay and firewall from the root put index.js at 174% of budget and exited 1, with a message naming the likely cause rather than just a number.

Budgets sit ~10% above current. verify.js gets more headroom because its IP range tables grow when the weekly refresh adds prefixes, and a routine range update shouldn't fail CI and train people to bump budgets reflexively.

Why it needed a new PR

Both commits were originally pushed to feat/pay-to-train after #23 had already merged, so they were stranded on a merged branch and never reached main. Cherry-picked here onto current main.

That's the fourth time in this session I've pushed to an already-merged branch. The habit that causes it: checking out a local branch by name without re-checking whether its PR is still open.

🤖 Generated with Claude Code

The root bundle had grown from 9,643 to 22,497 bytes — undoing most of the 65%
reduction in #21 — because payments, gateway, entitlement and firewall all
exported from the package root. Every consumer paid for them whether or not they
charged anyone, and the firewall recommender in particular is an offline
analysis tool that will never execute in middleware.

  dist/index.js     22,497 -> 11,608 B   (8,695 -> 4,546 gzipped)
  dist/pay.js                 10,861 B   opt-in
  dist/firewall.js             6,821 B   opt-in

Entry points now:

  @apideck/agent-analytics            detection, classification, agentPolicy, trackVisit
  @apideck/agent-analytics/verify     Web Bot Auth + published IP range tables
  @apideck/agent-analytics/payments   402 challenges, gateways, entitlements
  @apideck/agent-analytics/firewall   WAF recommendations (offline)
  @apideck/agent-analytics/markdown   Markdown-twin negotiation

agentPolicy stays in the root: classification without a policy to apply it to is
half a library, and it costs ~2 kB. That is why the root lands at 11.6 kB rather
than back at 9.6 kB.

README gains an entry-point table with the real numbers, and every import
example in the README, the testing guide and the site now points at the right
subpath. Verified from a clean install of the packed package that all five entry
points resolve.

Found while pulling accurate figures for the site's install section, which was
still advertising "216 tests" and "CI on Node 18, 20 and 22" — Node 18 was
dropped in 0.12 for lacking globalThis.crypto, so the site was claiming support
for a runtime the library refuses to run on. Both corrected.
The root entry ships in every consumer's edge middleware and runs on every
request, so its size is a feature rather than a vanity metric. It silently
doubled anyway: #21 cut it from 27.7 kB to 9.6 kB, then payments, gateway,
entitlement and firewall were each exported from the root by reflex and it
climbed back to 22.5 kB. Nothing failed. Every test passed. It surfaced weeks
later only because someone needed an accurate figure for a marketing page.

Same shape as the unpublished-0.10.0 bug and the agentIntent divergence: the
mechanism existed, nothing asserted the invariant, and it stayed broken until
something unrelated exposed it. So assert it.

scripts/check-size.mjs measures each entry gzipped against size-budget.json and
fails the build over budget. Zero dependencies, in keeping with the package —
node:zlib does the work.

  entry               gzipped     budget  used
  dist/index.js       4.44 kB    4.88 kB   91%
  dist/verify.js      6.25 kB    7.42 kB   84%
  dist/pay.js         4.05 kB    4.49 kB   90%
  dist/firewall.js    2.80 kB    3.13 kB   90%
  dist/markdown.js    1.03 kB    1.27 kB   81%

Verified by reintroducing the exact regression — re-exporting pay and firewall
from the root put index.js at 174% of budget and exited 1, with a message
naming the likely cause rather than just a number.

Budgets sit ~10% above current. verify.js gets more headroom because the
published IP range tables grow whenever the weekly refresh adds prefixes, and a
routine range update should not fail CI. Raising a budget is deliberate:
`npm run size -- --update`, and justify it in the commit.

The table prints on every run, not only on failure, so the trend is visible in
CI logs.
@Gdewilde
Gdewilde merged commit 107e304 into main Aug 2, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant