ci(webv2): judge byte budgets as a delta against the base branch - #253
Merged
Conversation
- Ceilings are derived at check time: the lower of max(1%, 4 KB) over the base-branch reference and max(10%, 32 KB) over the committed baseline; request counts take the lower count. `limits` blocks leave both baselines. - Every push to main caches versioned build/browser reference files by SHA; PRs into main restore their base's entry (or the latest main) and fall back to the committed baseline, logged and annotated, when none applies. - The re-record remedy is appended only for byte/request failures; source owners stay pinned exactly. ARCHITECTURE.md documents the derivation.
lstein
requested review from
JPPhoto,
Pfannkuchensack and
blessedcoolant
as code owners
September 13, 2026 19:52
…-gating # Conflicts: # invokeai/frontend/webv2/performance/architecture-baseline.json # invokeai/frontend/webv2/performance/browser-baseline.json
…-gating # Conflicts: # invokeai/frontend/webv2/performance/architecture-baseline.json # invokeai/frontend/webv2/performance/browser-baseline.json
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
A pull request's webv2 byte budgets were checked against ceilings committed in
performance/*-baseline.json— pinned at the captured measurement forownedRawBytesand 1% above it for the rest. Because those are absolute totals against a snapshot, a PR failed for growthmainhad already accepted since the last capture, and a PR left open whilemainmoved failed again for the same unchanged diff. One 64-byte change forced five baseline re-records in a day across three different metrics, each surfacing only after the previous was raised.Now a pull request into
main(and a merge-group run) is judged as a delta against the base branch's own measurements. Every push tomainwrites two small, versioned reference files and stores them in the Actions cache under its SHA; a PR run restores the entry for its base commit — or, if that commit has none (amainjob that failed, is still running, or was a merge-queue batch), the most recentmainentry — and the gate derives each ceiling from that. The gate logs which reference it used, or that it fell back to the committed baseline and why.Each ceiling is the lower of two bounds: an allowance of
max(1%, 4 KB)over the baseline being compared against, and a hard ceiling ofmax(10%, 32 KB)over the committed baseline. The allowance is what lets a PR answer only for its own bytes. The hard ceiling is what keeps the committed file the outer bound a person has reviewed: without it, allowance-sized merges could compound indefinitely with the committed numbers having no effect before a merge, and a deliberate downward re-record would not bite untilmainnext pushed. Request counts carry no allowance under either: their limit is the lower of the two counts.What is deliberately unchanged:
sourceOwnersand every structural rule still come from the committed baseline and are pinned exactly — a module entering a route's initial graph fails at zero bytes. That check is what catches architectural regressions; the byte budgets only need to catch gross size changes. Timing enforcement stays off.Pushes to
maincompare against the committed files. Amainthat has outgrown the allowance since its last capture therefore fails rather than having its ceiling ratchet up unreviewed — the failure text says so and names the re-record commands (pnpm test:performance:build:update-baselineis new; the browser one existed). That remedy is appended only when a byte or request limit failed — an owner-graph leak or importer-count failure gets no advice to re-record, since re-recording would erase exactly that signal. That keeps.github/AGENTS.md's "do not silently relax budgets" rule intact.Two consequences of deriving ceilings instead of committing them: the
limitsblocks disappear from both baseline files (only measurements are recorded, and the duplicated-number merge conflicts that came with every re-record go with them), and the deadgetLegacyOwnedLimitzero-headroom fallback is removed along with both limit-derivation helpers.ARCHITECTURE.mddocuments the derivation, the reference, and the recovery commands.QA Instructions
All from
invokeai/frontend/webv2on the final candidate; each gate run alone, since the browser stage binds a preview server and a mock backend and two gates race ondist/and the report files regardless of ports (INVOKEAI_PERFORMANCE_PORT/INVOKEAI_PERFORMANCE_BACKEND_PORTmove them).pnpm run test:performance:contracts— pass. Covers the allowance, the hard ceiling over the committed baseline (includingminon request counts), the browser gate driven throughcheckBrowserRouteBudget(within/beyond allowance, exact request counts, reference byid:profilewith both origins named, theactivatedResourcesNaN guard), reference-artifact shape, version and metric-set incompatibility, named errors for a non-reference file, and rejection of the removedlimitskeys, and the loader's missing/invalid-JSON/no-version wiring faults (42 cases).pnpm lint— pass (format, oxlint--deny-warnings, tsc, architecture policy).INVOKEAI_PERFORMANCE_PORT=4276 INVOKEAI_PERFORMANCE_BACKEND_PORT=4277 pnpm run test:performance:architecture— no reference — pass, exit 0. LogsByte budgets: against committed baseline captured 2026-09-11.andResource budgets: against committed baseline captured 2026-09-07.; both reports recordreference: null; the run wrotebuild-reference.json(1,089 B) andbrowser-reference.json(7,053 B — the cached payload was 1.24 MB before).WEBV2_PERF_REFERENCE_DIR=artifacts/perf-reference WEBV2_PERF_REFERENCE_LABEL=local-self, the directory holding this tree's own two reference files (a self-comparison, which exercises the delta path end to end in both gates) — pass, exit 0; loggedByte budgets: delta against base-branch reference local-self captured 2026-09-13T19:42:11.057Z.andResource budgets: delta against base-branch reference local-self captured 2026-09-13T19:43:45.399Z.;build-report.jsonrecordsreference: {applied: true, capturedAt, label: "local-self", reason: null, uncoveredRoutes: []}andbrowser-report.jsonrecords the same shapeWEBV2_PERF_REFERENCE_DIRset to a directory lacking the files throws naming the variable and path (loadPerformanceReference), and a reference route missing a metric throws before any NaN ceiling can be derived (contract-tested on both gates).Not run:
actionlintis not installed here; the workflow YAML parses and was reviewed by five independent read-only reviewers. The cache save/restore steps can only be exercised in Actions — the first push tomainafter merge bootstraps the reference, and until then PRs restore nothing and compare against the committed baseline, which the log and a::warning::annotation state.Review
Three independent read-only reviewers (correctness/spec; architecture, operational safety, performance; test value and gaps) reviewed the first candidate. Material findings, all resolved: budgets were advisory before merge (a PR could grow past the committed file because only
mainwas compared against it — hence the hard ceiling over the committed baseline); an artifact-schema change in a PR would have wedged CI on the base's cached reference (henceschemaVersion+ metric-key list with a logged fallback); amainjob that failed early left a sticky cache hole and merge-queue batches never matched (hencerestore-keyson the prefix); the reference tests passed vacuously (routes and decoys added, the browser gate driven throughcheckBrowserRouteBudget, source-owner pinning asserted under a reference); the re-record remedy was appended to owner-graph failures (henceisBudgetFailure); reports did not say which reference was used (hencelabel,reason,uncoveredRoutes).A blocker-only re-review of the resulting candidate found none and confirmed each finding resolved; six of its minors were fixed (version judged before shape, browser report records
uncoveredRoutes, remedy scoping, wording, stage/warn only when the gate ran, merge groups scoped tomain) and a third blocker-only review of those found none. One minor from that round was then taken — a reference file with no integerschemaVersionis a wiring fault and throws, rather than being reported as drift — covered by a contract test; that two-line change was verified by the contract suite andpnpm lintand not by re-running the full gates, whose path (a version-1 file) it does not alter. Remaining accepted minors: an unknown per-route key in a version-1 reference throws (only reachable by hand-editing), a malformed committed baseline throws before the report is written, and--update-baselinewith the reference variable set recordsapplied: falsewith no reason (local-only mode).Compatibility / Rollout
limits,resourceLimitsandactivatedResourceLimitsare no longer valid keys; the validators reject them (tested), so a stale re-record cannot reintroduce the old shape.--update-baselineon either gate writes the new shape and does not read a reference.build-reference.json,browser-reference.json) carryschemaVersionand their metric key list. A PR that changes the metric set, or a revert, reads a reference recorded by a different checkout: that is detected and falls back to the committed baseline with the reason logged, never a hard failure. A setWEBV2_PERF_REFERENCE_DIRwhose file is missing or malformed does fail, naming the variable and path — a wiring fault must not pass as "no reference".mainafter this merges; that push touches webv2, runs the gate, and records itself. Until then PRs fall back (logged, annotated). Amainjob that ran the gate but failed before its browser stage records nothing for its SHA and says so in a::warning::; one that left webv2 untouched records nothing silently. Later PRs restore the most recentmainentry either way, so holes do not propagate.mainpush; they restore the most recentmainentry. Merge groups targeting any other branch receive no reference.main) get no reference and compare against the committed baseline — a documented limit, not addressed here.mainjob hits an existing key;actions/cache/savewarns and skips.Checklist
limitskeys rejected by the validators; no application data)What's Newcopy (if doing a release after this PR)