ci(build-check): the lock-bump fast path must prove it CANNOT move a closure (public rebuilt from source for 30m, #1163) #1164
No reviewers
Labels
No labels
backlog
bug
enhancement
in-progress
review
todo
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
ymrtech/nix-config!1164
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "fix/lockbump-closure-neutral"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
What happened, and what this fixes
Tonight's private-repo merge (the clientsv VIP drain, #208) reached this repo as a lock-only bump
(#1162) and
NixOS Build Check / Build changed host closureswas SKIPPED on it. Nothing had built orcached the closure, so public's deploy compiled it from source: run #10760 took 30m31s, ~28 min of
that building
perland friends on 1 vCPU under load 15–22, with the host's own services (Forgejo,Postgres, AdGuard, Matrix…) starved behind it. vpn only looked fast because the same closure happened to
be in its store already.
The cause is in this file's scope job: the PROVISIONING FAST PATH decides on shape —
provision-lock-bump.shrequires exactly one changed file (flake.lock) whose changed lines are thenix-config-privatepin fields. Every private-repo CD lock bump has that shape, so a private modulechange was scoped to
vpn alone (no eval, no build). The safety property the fast path's comments reliedon ("such a bump leaves every host's toplevel
.drvPathbyte-identical") was measured for clientprovisioning and is not checked — and, as this PR also records, is not even true for vpn.
The fix: ask the commits, not the shape
.forgejo/scripts/lock-bump-closure-neutral.sh <base> [head] | --prevclonesnix-config-privateblobless (45 s clone / 30 s per fetch, bounded because this job has a 5-minute budget and both CD chains
poll it by name), fetches the two revs named by
flake.lockat each end, and requires every changedpath to be data/docs/CI:
docs/**, top-level*.md.forgejo/**clients/*.nix,clients/.gitkeep.gitignore,.gitleaks.tomlEverything else —
modules/**,scripts/**(read byvpn-network.nix),pkgs/**,secrets/**,topology/**,flake.nix, our ownflake.lock— is not neutral, and a mixed change is notneutral (one non-allow-listed path fails it). Unprovable is never a pass: a clone/fetch/diff/pin
failure widens to every deployable host, so the closure is evaluated, built and pushed to Attic. Wired into
both branches — the PR path and the post-merge push, which is the one that burned public.
Why the peer files stay fast (a measurement, not a preference)
clients/**.nixarepkgs.writeTextpeer files compiled into the wg1/wg2 applier units, so a clientadd/revoke does move vpn's closure — measured on this fleet, 2026-09-29:
So the "byte-identical" justification in this file was wrong, and every comment that claimed it now
says what is actually true. What keeps the provisioning path fast is that nothing compiles for it: the
moved derivations are the peer file, the unit files,
etcand the toplevel, which vpn's own switchproduces in seconds — while an all-hosts eval + closure build is 2m40s paid per client op on a host
that does not need the store paths, and the client-op path is a seconds-budget path.
Verified
(
rc=1, namingmodules/clientsv-vip-drain.sh,modules/clientsv-vip.nix,scripts/clientsv-vip-drill.sh); five provisioning-shaped bumps stay fast (rc=0); and four past bumpsthat also touched
modules/adguard*,modules/vpn-network.nix,secrets/vpn.yamlorscripts/pg-pitr*are refused — exactly the bumps the old shape-only rule skipped a build for.bash -nclean (extracted from theworkflow, not eyeballed).
gitea-runnercloneshttps://git.ymrtech.com/ymrtech/nix-config-private.gitwith no prompt — the same thing the deploypipeline's
prepjob already does on the[gate]pool this job runs on.scope job so the rule cannot rot.
Closes #1163.