Skip to content

Refuse a page that misses a line of the speed budget [#34] - #159

Merged
iderex merged 1 commit into
mainfrom
the-byte-budget-as-a-gate
Aug 13, 2026
Merged

Refuse a page that misses a line of the speed budget [#34]#159
iderex merged 1 commit into
mainfrom
the-byte-budget-as-a-gate

Conversation

@iderex

@iderex iderex commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

What was wrong

decisions/0005-the-speed-budget.md says a budget written as an intention is not
a budget, and then nothing in this tree read any of its numbers. Every line of it
that can be decided by reading the bytes a build wrote was a sentence in a
document:

go run . invariants | grep -c -i 'budget'
0

Run 2026-08-13 against 562e918. So a page could grow past twenty kilobytes, a
stylesheet past twelve, or pick up a font face, and the gate would say nothing.

What this does

The numbers move into one place, and that place is what the rows read. A published
budget and an enforced budget that are two copies of one set are two copies that
disagree the first time either moves, and the disagreement is invisible: the page
goes on printing a limit nothing holds anybody to.

Four rows read them, all from the produced output and with no network and no
browser:

  • a document at or past 20 KB, uncompressed
  • an inlined stylesheet at or past 12 KB, counting every style element on the page
    together, because what the reader waits for is the document and a second element
    is not a second budget
  • a declared font face anywhere on a produced page
  • the landing page asking for more images than the record allows it

Each refusal names the page, the measured number, the limit and the record the
limit is argued in, because a budget failure that says only that the build failed
makes the next person measure by hand.

The font row is not a second spelling of the row about a foreign domain. That one
reads the host a reference reaches, so a face this site served itself satisfies it
and costs the reader exactly what the budget puts at zero. That is measured below
rather than argued.

The image line needed one thing the table could not do. It is written for the
landing page and says nothing about the other pages, and a row is handed one page's
bytes without its name, so a row deciding that line would have applied it
everywhere and refused more than the record does, which is a rule nobody argued. A
row may now name the one produced path it is about. A row naming a path the build
did not write reports that it examined nothing rather than passing, so a page
renamed out from under one of these does not leave a green row behind.

Two lines of the record are deliberately absent from the file the rows read.
Largest contentful paint and cumulative layout shift are what a browser does with
these bytes rather than properties of the bytes, so they are not constants here:
putting them in would say a check reads them.

Closes

Nothing. #34 stays open, and the reason is in the issue: its done-when also asks
that the design system page print the same numbers because both read one source,
and that page is not produced by this build yet.

What was run

go run . ci
gate: 6 legs, in order: format, vet, test, build, links, invariants
  needs-network was not asked for. Asking costs a request to the public name from whatever machine runs it, and a verdict that moves when somebody else's service does rather than when this tree changes. Ask with: go run ./harness/needs-network
  format: ok, 44 file(s)
  vet: ok
  test: ok, 21 test file(s)
  build: ok, 5 file(s)
  links: every reference that stays inside this site resolves to a file the build wrote
  invariants: ok, 34 rule(s) decided, 1 owed and not decided
6 of 6 legs ran. None was skipped.

needs-network was not asked for and the run names it. No test was skipped, and
none of this needs a display, a browser or elevation. What the rows report over
the tree as it is:

go run . invariants | grep -E 'budget|font|landing'
  page-fits-the-markup-budget: ok, 4 file(s) of every page the build produced
  page-fits-the-stylesheet-budget: 4 file(s) of every page the build produced carried no inlined stylesheets, so this rule decided nothing
  page-downloads-no-web-font: ok, 4 file(s) of every page the build produced
  landing-page-asks-for-at-most-two-images: ok, 1 file(s) of every page the build produced

The stylesheet row says it decided nothing rather than ok, because no page inlines
one yet, and a green mark over a page with no stylesheet reads as a size that was
checked. The image row read one file, which is the narrowing working.

The markup row against the real tree, with the landing page's prose grown the way
prose grows:

go run . invariants
  page-fits-the-markup-budget: REFUSED, 1 violation(s)
    it refuses a produced page whose whole document is 20480 bytes or more, uncompressed
    dist/index.html: this page is 23310 bytes and the budget in decisions/0005-the-speed-budget.md puts a document under 20480

The font row against the real tree, with a face served from this site's own root
put in the frame:

go run . invariants
  page-downloads-no-web-font: REFUSED, 4 violation(s)
    it refuses a produced page declaring a font face that fetches a file, wherever it is served from
    dist/index.html: line 16 declares a font face, and the budget in decisions/0005-the-speed-budget.md puts web font downloads at 0
    dist/privacy/index.html: line 16 declares a font face, and the budget in decisions/0005-the-speed-budget.md puts web font downloads at 0
    dist/legal/index.html: line 16 declares a font face, and the budget in decisions/0005-the-speed-budget.md puts web font downloads at 0
    dist/404.html: line 16 declares a font face, and the budget in decisions/0005-the-speed-budget.md puts web font downloads at 0

and on that same tree, the row this one is not a second spelling of:

go run . invariants | grep 'output-references-no-domain-outside-the-allowlist'
  output-references-no-domain-outside-the-allowlist: ok, 5 file(s) of every file the build produced

That is the whole argument for the row existing, measured rather than asserted.

Both experiments were undone before the commit, and the gate run at the top of
this section is the tree as it is being pushed. Every command in this section run
2026-08-13 at 4211d28.

The narrowing is proved in the suite rather than by hand, because doing it by hand
would mean putting three images in the real frame: a fixture frame putting three
images on every page reds the image row once, names the landing page and its
count, and does not judge the second page the run produced. The boundary is proved
too, at the limit and one byte under, because a limit written as a number has a
byte nobody tests.

The means

Go, the same invariant table that decides every other property of a produced page,
and a package holding four constants. No language, dependency, runtime or network
is added, and the rows read the output a build just wrote, which is what keeps the
whole set decidable without a browser. All four rows are shown to bite, two against
this tree and all four against fixtures in the ledger the suite already keeps, and
each refusal carries the measurement rather than a verdict.

Who read it

Nobody else. This change carries no second reader, and the evidence above stands
in place of one.

The budget record says it is written as numbers a build can miss rather than as
an intention, and until now nothing read the numbers. Every line of it that can
be decided by reading the bytes a build wrote was a sentence in a document.

The numbers move into one place that the check reads, so a published budget and
an enforced budget cannot be two copies that disagree. Four rows read them: a
document at or past twenty kilobytes, an inlined stylesheet at or past twelve, a
declared font face anywhere on a produced page, and the landing page asking for
more images than the record allows it. Each refusal names the page, the measured
number, the limit and the record the limit is argued in, because a budget failure
that says only that the build failed makes the next person measure by hand.

The font row is not a second spelling of the row about a foreign domain. That one
reads the host a reference reaches, so a face this site served itself satisfies it
and still costs the reader exactly what the budget puts at zero. Measured: with a
face served from this site's own root in the frame, the origin row is green on
every produced file and the font row refuses all four pages.

The image line needed one thing the table could not do. It is written for the
landing page and says nothing about the others, and a row is handed one page's
bytes without its name, so a row deciding it would have applied it to every page
and refused more than the record does. A row may now name the one produced path
it is about. A row naming a path the build did not write reports that it examined
nothing rather than passing, so a page renamed out from under one of these does
not leave a green row behind it.

Two lines of the record are deliberately not here. Largest contentful paint and
layout shift are what a browser does with these bytes rather than properties of
them, so they are absent from the file the rows read instead of sitting in it as
constants nothing decides.

Signed-off-by: Nils Lehnen <30603423+iderex@users.noreply.github.com>
@iderex iderex added the tests label Aug 13, 2026
@iderex iderex self-assigned this Aug 13, 2026
@iderex
iderex merged commit 27c9e9a into main Aug 13, 2026
17 checks passed
@iderex
iderex deleted the the-byte-budget-as-a-gate branch August 13, 2026 08:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant