/teal-sea
teal-sea / zeta-labstate of record · compiled 02 Nov 2026 · revision b09fa4dd · source

Library · hunts/rung3_ball_production/PILOT.md

PILOT: one prime tower, composite atoms and one big-box site at Lean v4.35.0-rc2

7,483 words · 679 lines · source

Hunt #145, lane rung3-pilot, 2026-10-21. Grade: measured (one box, one session, one route per number) for every cost below. The kernel outcomes are observations of what Lean accepted and refused on this box. The only kernel-checked objects are the pilot site's two theorems, whose axioms are printed in section 2; they say that ‖DH s‖ ≤ 25499/10000 on one box of the big square's boundary, and nothing more. No production shard was launched, and davenport_heilbronn is still unproved: no file emits the assembly module (section 6).

Every table between <!-- table:... --> markers is rendered by report.py from pilot.json (render_docs.py writes them in), and test_rung3_ball_production.py fails if a table here differs from what pilot.json renders. Numbers in the prose restate those tables or are pinned by that test.

1. Environment and what ran

<!-- table:setup -->

itemvalue
box<box>: 32 vCPU (Intel Core Processor (Haswell, no TSX)), 115.1 GiB, no swap, shared
Leanleanprover/lean4:v4.35.0-rc2; Lean (version 4.35.0-rc2, x86_64-unknown-linux-gnu, commit 11acb17ec6b07a8f9e9173e6845197929540936b, Release)
Mathlib065356127b1d (lean/lake-manifest.json)
Lake packages9; every package's git toplevel is the package itself: yes; every HEAD equals its manifest revision: yes
ZetaLean modules built15: Ball, BallCertSupport, BallTerm, DHAnalytic, DHAssembly, DHCertSupport, DHTailBound, DHTailBound2, DHZeroCriterion, DavenportHeilbronn, DirichletEval, IntervalCExp, IntervalExp, OracleDH, Rigor

<!-- /table:setup -->

Lake ran only in this worktree's lean/.lake, which lake exe cache get filled from scratch in the first attempt; no .lake was copied (compute rule 7), and each package's git toplevel and HEAD were checked again when pilot.json was written. The 15 ZetaLean modules are Ball, BallTerm, BallCertSupport, DHCertSupport, DHAssembly and DHTailBound2 with their imports, built from this branch's sources. scripts/72 is main's version (MISSION.md, "Starting point"); tests/test_rung3_ball_compile.py passes against it (22 tests). Every Lean process ran through pilot.py, which waits before each run while the 1-minute load average is above a threshold (64 before the site compile and the fault recompiles, 96 before single small files) or MemAvailable is below 30 GiB, and records argv, working directory, LEAN_PATH, wall time, user and system CPU and peak RSS from wait4, and the load before and after.

The pilot site was generated and compiled with these two command lines, which pilot.py site ran and pilot.json records verbatim. The work directory is outside the tree, and any empty directory replays it. The generator is deterministic: the first attempt's ten sources and this run's are byte-identical (generator_replay in pilot.json compares their SHA-256), and pilot.json keeps each source's hash:

<!-- table:commands -->

# from the worktree root; Lean from elan
export PATH="$HOME/.elan/bin:$PATH"
cd .
.venv/bin/python scripts/60_rung3_generate.py lean/cert/rung3_plan2.json <cache>/rung3-ball-production-pilot-2/site/B_right_06/gen --arith ball --shard --only B_right_06
cd .
.venv/bin/python scripts/72_rung3_ball_compile.py --lean-project lean --output-root <cache>/rung3-ball-production-pilot-2/site/B_right_06/out --jobs 1 --module-jobs 1 <cache>/rung3-ball-production-pilot-2/site/B_right_06/gen/CB_right_06.manifest.json

<!-- /table:commands -->

2. Kernel outcomes

B_right_06 is one of the three big boxes with the smallest K in plan v2 (K = 17, with B_bottom_23 and B_top_22; big boxes run to K = 61, grid points from 85 to 114, the centre is 361), the same K as the unsharded whole-file record in scripts/67. It has 23 prime towers (p = 2 to 89), 48 composites, 68 nonzero terms and N = 85 series terms, 774 atoms by scripts/67's count. scripts/60 split it into 10 modules: Base, five prime shards, the Primes barrel, Composites, Terms and the root CB_right_06, which states

theorem upper_B_right_06 {s : ℂ} (hS : SI_B_right_06.contains s) :
    ‖DH s‖ ≤ (((25499/10000 : ℚ)) : ℝ)

scripts/72 compiled the ten in manifest order at one process, each with exit 0, and recorded the site ok (section 3 has the per-module record). pilot.py axioms then printed the axioms of upper_B_right_06 and of enc_B_right_06 (the enclosure theorem it rests on) from the compiled oleans: propext, Classical.choice and Quot.sound, and no sorryAx. That is the kernel-checked content of the pilot, one box of the big square's boundary bounded by M.

Planted faults in the site

<!-- table:faults -->

faultchange plantedexpected refusalerrors atdriver exitmodule refusedmodules recompiled
tower-radPrime_89.Sq_10 radius lowered by one unit of its denominator (13609794713164397/18446744073709551616 to 3402448678291099/4611686018427387904)Prime_89.ev_mul_29Prime_89.ev_mul_29, hp_B_right_06_891CB_right_06/Primes04.lean1
tail-radtail radius r set 9.6e-13 below the tail bound, all 3 occurrencesenc_B_right_06enc_B_right_061CB_right_06.lean1
MM set 9.1e-13 below u + rad of the inflated ballupper_B_right_06upper_B_right_061CB_right_06.lean1

<!-- /table:faults -->

Each fault was planted by pilot.py fault in a copy of the generated sources, and the copy was recompiled by the same driver against a copy of the clean output root, so scripts/72 skipped every module whose source hash was unchanged and recompiled only the touched one. Two of the three are emitted radii: the last squaring of the largest prime's tower (Prime_89.Sq_10, radius lowered by one unit of its denominator 2^64) and the tail radius r of enc_B_right_06 (set just below the tail bound that its hypothesis hr must clear, in all three places it occurs). The third lowers M in upper_B_right_06 to just below the bound the enclosure delivers. In each case Lean refused the module at the declaration named in the table: the norm_num step that proves the perturbed equality or inequality fails, so no proof term for it reaches the kernel, the module writes no olean, and the driver marks the site failed and schedules none of its dependents. The radius fault was caught twice inside its module: at ev_mul_29, whose product no longer equals the literal, and at hp_B_right_06_89, where the site's own copy of that ball (Pow_B_right_06_89, the ball the rest of the site reads for 89^(-s)) no longer matches the perturbed Sq_10.

eps': the centre theorem's last step, with the enclosure as a hypothesis

<!-- table:centre -->

probeeps' statedu + rad of the centre balllean exit
control5.000000e-043.855242e-040
eps-above-bound3.855242e-043.855242e-040
eps-at-bound3.855242e-043.855242e-041
eps-half2.500000e-043.855242e-041

<!-- /table:centre -->

eps' (1/2000 in plan v2) appears in one emitted theorem, centre_lt_centre : ‖DH z_centre‖ < ((1/2000 : ℚ) : ℝ). Its site has K = 361, 278 prime towers and 10,233 atoms by scripts/67's count; compiling it is production, so it was generated (Python only) and not compiled. pilot.py centre-eps compiles that theorem's last step verbatim, from the emitted B_centre and z_centre literals, with the enclosure henc : (B_centre.inflate r).contains (DH z_centre) taken as a hypothesis instead of proved, at four values of eps'. The step needs u + rad < eps', where u bounds the centre's modulus and rad is the inflated radius. Lean accepted the plan's value and the next multiple of 2^-64 above u + rad, and refused u + rad itself (the claim is strict) and half the plan's value, each refusal an error inside the probe's theorem. This shows that the eps' literal is checked against the emitted enclosure, to the last unit; it does not show that the centre's enclosure compiles. The other place eps' enters, the cell hypotheses eps' + L*h/2 <= beta, lives in the assembly module (section 6).

The mirror and the kernel on B_right_06

The kernel accepted every emitted equality of the site, ending with evB_B_right_06, which says that the computed partial sum plus correction equals the literal B_B_right_06. So the kernel's value of that ball is the emitted literal. pilot.py mirror compares it with the bit-exact Fraction mirror as scripts/65 runs it. The centres agree exactly. The radii do not: scripts/65's ball is wider by exactly 738083614955/2^64 (about 4.0e-8), which is the radius scripts/63's cpowBox gives to 1^(-s), because scripts/65 evaluates the m = 1 term through the generic power chain while the generator seeds it as the exact ball (1, 0, 0) (Pow_B_right_06_1 in the site's Base module). A copy of scripts/65's assembly with m = 1 seeded the same way reproduces the emitted literal bit for bit, and the unseeded copy reproduces scripts/65's value. So on this site the mirror and the kernel agree once that seed is accounted for, and scripts/65's recorded normBound (the value in lean/cert/rung3_ball_validation.json) is the larger, conservative one. The hunt test pins each of these equalities.

3. Cost per atom and per module

<!-- table:cost -->

quantityv4.33.0-rc2 recordthis pilotratio
CPU-s per prime-tower atom, scripts/68 tower at p = 2, 171 KB of text (net of import)1.25042.0896 (1.7822 to 2.3673, 3 runs)1.67
CPU-s per prime-tower atom, scripts/68 tower at p = 2, with lean -j1not measured2.2548 (2.1798 to 2.3951, 3 runs)1.80
CPU-s per prime-tower atom, scripts/68 tower at p = 2, with Lean's linters off (-Dlinter.all=false)not measured2.2527 (2.1989 to 2.3045, 3 runs)1.80
CPU-s per prime-tower atom, scripts/68 tower at p = 1801, 532 KB of textnot measured2.2259 (2 runs)1.78
CPU-s per prime-tower atom in the site's prime shards (p = 2 to 89, 23 towers)not measured2.42191.94
CPU-s per composite atom, scripts/66, 100 atoms (net of import)0.550.4865 (3 runs)0.88
CPU-s per composite in the site's Composites module (48 composites)not measured0.67411.23
CPU-s per nonzero term in the site's Terms module (68 terms)not measured0.513
root module net CPU-s (term function, partial sums, assembly; N = 85)not measured16.0
import-only CPU-s per module, Base imports (median)4.57610.0792.20
import-only CPU-s per module, root imports (whole site loaded)not measured9.805
peak RSS added by importing the site's other oleans (root probe minus Base probe), per olean byte importednot measured0.66
peak RSS per process, largest module (wait4)not recorded7.83 GiB
of which anonymous, largest module (sampled each second; the rest is file-backed olean mappings, shared between processes)not recorded1.91 GiB
cores busy per process, site compile at one process (CPU / wall)not recorded1.38

<!-- /table:cost -->

<!-- table:modules -->

moduletowerscompositestermssource KBolean MBwall sCPU simport CPU snet CPU speak RSS GiBanon RSS GiBloadwarnings
CB_right_06/Base.lean1.40.118.011.110.11.06.330.40n/a2
CB_right_06/Primes00.lean71,853.447.7217.1521.69.9511.77.831.9170308
CB_right_06/Primes01.lean51,685.835.6261.9375.610.3365.37.441.4791220
CB_right_06/Primes02.lean51,789.835.9323.3387.610.8376.87.441.50112220
CB_right_06/Primes03.lean51,961.136.5258.3358.210.2348.17.431.4895220
CB_right_06/Primes04.lean1396.87.9167.882.112.969.26.580.668544
CB_right_06/Primes.lean0.20.024.410.210.5-0.36.400.40840
CB_right_06/Composites.lean4855.37.030.743.311.032.46.620.59840
CB_right_06/Terms.lean6889.85.425.545.810.934.96.590.58740
CB_right_06.lean35.03.820.625.89.816.06.560.52650
site total2348687,868.6179.81,347.51,861.4106.31014

<!-- /table:modules -->

Every rate is CPU net of an import-only run with the same imports: import ZetaLean.BallTerm for the scripts/68 and scripts/66 files, and for each site module a probe file holding that module's own import lines (pilot.py imports, two runs each, the median taken). The import itself is a large and noisy share on this box, so a file must carry enough atoms to rise above it: ten composite atoms cost a few CPU-s against an import of about ten, and the composite rate is taken from a file of 100 (pilot.json keeps both). In the site, the five prime shards give the in-situ tower rate, and the Composites, Terms and root modules give the composite, term and root rates; those three modules import every prime shard's olean, and their probes say what that import costs. The modules table also counts the Lean warnings in each module's log, almost all of them Mathlib's <;> style linter.

Threads per process

<!-- table:threads -->

CB_right_06/Primes02.lean (5 towers)default threads (scripts/72)lean -j1
CPU-s387.6369.3
wall s323.3463.7
CPU-s net of the module's import probe376.8358.5
net CPU-s per tower atom2.5122.390
peak RSS GiB (anonymous)7.44 (1.50)7.28 (1.33)

<!-- /table:threads -->

scripts/72 runs lean with its default thread count. Across the two attempts the same prime shards ran at very different parallelism (CPU over wall, from the replay table below) while their CPU totals stayed within the spread shown there, so on this evidence threads move wall time and not CPU. One shard was also recompiled with lean -j1 (pilot.py shard, into a scratch olean, the site's own oleans untouched), and the scripts/68 tower was run at -j1 as well (the cost table's -j1 row). At -j1 the shard's CPU net of import came out slightly below its default-thread figure (threads table), and the tower's CPU per atom at -j1 fell inside the spread of its default-thread runs, while each single-threaded run's wall time was its CPU time stretched by the load. So threads per process change how production shares cores, not what it costs: a driver could run one thread per process and as many processes as it is given cores, at the anonymous memory per process the -j1 shard recorded (threads table, last row).

Replaying the timings

<!-- table:replay -->

modulesame sourcefirst attempt CPU s (wall s)this run CPU s (wall s)CPU ratiowall ratio
CB_right_06/Base.leanyes13.6 (19.1)11.1 (18.0)0.820.94
CB_right_06/Primes00.leanyes560.4 (166.3)521.6 (217.1)0.931.30
CB_right_06/Primes01.leanyes402.7 (135.1)375.6 (261.9)0.931.94
CB_right_06/Primes02.leanyes372.5 (117.4)387.6 (323.3)1.042.75

<!-- /table:replay -->

The four modules the interrupted first attempt compiled are byte-identical to this run's (same SHA-256), so they are a replay at a different load: the table gives the CPU and wall ratios. A second agent replaying the site with the two command lines of section 1 should compare CPU (user plus system), not wall time, and should expect agreement of the kernel outcome (every module exit 0) exactly and of CPU to roughly the spread shown here, given a similar load. Wall time moved far more than CPU between the two attempts for the same files (the last column).

4. Projection to the 215 sites

<!-- table:projection -->

componentcore-hours
prime towers (468,240 atoms)380.5
composites9.1
term products9.1
root and partial sums4.2
imports (4,696 modules)13.2
total, projected from this pilot416.0
atom-only, scripts/67 formula at the scripts/68 and scripts/66 rates of this box282.7
atom-only, scripts/67 formula at the in-situ rates of this site330.2
atom-only, scripts/67 as recorded (v4.33.0-rc2 rates)175.0
oleans left on disk, GB (scaled per tower, or per byte of tower text)123 to 146

<!-- /table:projection -->

<!-- table:wall -->

concurrent processeswall hours, one core per processwall hours at 1.38 cores per process (capped at 32 vCPU)
1626.018.8
2020.815.1
2417.313.0

<!-- /table:wall -->

<!-- table:sensitivity -->

tower cost modeltower core-hourstotal core-hourswall hours at 16wall hours at 24
in-situ shard CPU per source byte (the request uses this one)380.5416.018.813.0
in-situ shard CPU per tower, flat in the prime315.0350.515.910.9
least-squares fit, per tower plus per byte, over scripts/68 towers and the shards311.6347.115.710.8
lean -j1 shard CPU per source byte, one core per process350.6386.124.116.1

<!-- /table:sensitivity -->

How the projection is built (report.py, projection): for each of the 215 sites, from its K alone, the primes up to 5K + 4 (multiples of 5 skipped, as the generator skips them) each get a tower whose Lean text scripts/69 emits at one representative point per site kind; the rest are composites; there are 4K nonzero terms and N = 5K series terms. Modules are packed under scripts/69's 2 MB cap, and every module pays the Base import probe, plus, for the modules that import the site's prime shards, a per-byte cost of those oleans (zero when the pilot's probes could not resolve it). Composite, term and root costs are the pilot's in-situ rates, scaled linearly. Towers are priced two ways from the five prime shards, per byte of tower text and flat per tower, and the request uses the larger. That rule was fixed at 22:07 UTC, before the p = 1801 tower ran, from four shards of which two had been miscounted (RUNS.md, with its correction). With the right counts the shards cost about the same CPU per tower (69 to 75 CPU-s net) from p = 2 to p = 89 while their text roughly doubled, which favours the flat model in that range; above p = 89 only the scripts/68 tower at p = 1801 speaks (cost table). That tower cost close to the same CPU per atom as the p = 2 tower with about three times the text, so the flat model holds at the top of the plan's primes as well, and the per-byte total most likely overprices the towers. The rule is kept as recorded, so the flat total is the low end of the range and the per-byte total is what the request uses. The wall table divides the core-hours by the cores n processes would hold, at one core each or at the pilot site's measured cores per process, capped at the box's 32 vCPU. That measured figure comes from a loaded box; the replay table shows the same shards running at roughly twice the parallelism at a lower load, so on a free box the cap binds sooner and the capped column is the one to read. Both columns assume the CPU per atom stays at this pilot's rate when production saturates the box, which this pilot could not test.

5. What these numbers do not say

6. What is not built

scripts/60 emits one site theorem per point or box: upper_<id> (big boxes), beta_<id> (grid points) and centre_lt_centre. No script emits the module that combines the 215 of them with norm_deriv_DH_le, DH_lower_on_hcell, DH_lower_on_vcell and the square criterion (lean/ZetaLean/DHZeroCriterion.lean, line 185) into theorem davenport_heilbronn : davenport_heilbronn_statement. The cell hypotheses of that module, eps' + L*h/2 <= beta, are today checked only in Python by the generator (cell_requirements in scripts/60); in Lean they would be discharged in the assembly module, which does not exist. On this route, production of the 215 site theorems is necessary for the target and not sufficient.

7. Resumed 2026-10-26: the tower under decide +kernel

Measured, one session on the shared box, one Lean process at a time under nice -n 10, each run gated on a 1-minute load of at most 64 (the load before each run was 33 to 60). The instrument is decide_pilot.py; its raw records are in <cache>/rung3-ball-production-decide/runs.jsonl and its summary is the decide_kernel section of pilot.json, written by decide_pilot.py summarize (not by report.py, whose tables above are unchanged). Every file imports ZetaLean.BallCertSupport; "net" is CPU (user plus system, /usr/bin/time -v) minus the median of two import-only runs, 10.8 CPU-s, whose own spread is 1.2 CPU-s.

The threshold, written in RUNS.md before any run: a full tower at most 10 CPU-s net confirms a repricing of production to roughly 20 to 90 core-hours; above that the norm_num price (416 core-hours) stands.

file (prime 2 at the scripts/68 oracle point unless named)net CPU-s
scripts/68 tower, staged equalities by norm_num, full file (1 run)66.1
the same, equalities only (1 run)13.0
every staged equality by decide +kernel, full file (2 runs)43.4
the same, equalities only (2 runs)4.8
whole tower, dirichletTermBallB 32 20 64 2 10 2 S = lit := by decide +kernel (1 run)2.5
full tower through the whole decision: contains_dirichletTermBallB, its norm side condition by decide +kernel, then the decision (2 runs)3.4
the same at p = 1801 on B_right_06's input ball (radius 357/32768) (2 runs)5.3
one composite product (6 = 2 x 3), decide +kernel (2 runs) / norm_num (1 run)within the baseline's spread / 1.0
one kappa product (coeffBall 2 times the p = 2 ball), decide +kernel (2 runs) / norm_num (1 run)0.5 / 1.8

What the kernel did:

Verdict against the threshold. The staged variant misses it: 43.4 CPU-s net, because the staged equalities were never most of the tower's cost (13.0 of 66.1 CPU-s under norm_num); the containment theorem around them is. The whole-decision route meets it at 3.4 and 5.3 CPU-s net for the two towers measured. So the repricing holds for the whole-decision route. Priced the same way as section 4 (15,608 prime towers in the 215 sites, every other component kept at its 2026-10-21 price of 35.5 core-hours), production comes to 50 to 58 core-hours (decide_kernel.reprice in pilot.json) against the 416 of section 4.

That figure is an extrapolation, hedged here at its point of use. It comes from two towers in standalone files, not from shards. The 2026-10-21 shards cost 10 to 20 percent more per tower than standalone scripts/68 files. The other 35.5 core-hours could also fall under decide +kernel, which has not been measured beyond one composite and one kappa product. Most importantly, no generator emits the whole-decision tower yet: scripts/69 emits the staged form, and changing it is outside this hunt's write scope. A site compiled from today's generator still costs the section 4 price. PRODUCTION.md lists that change as a decision for the owner before launch.

8. Resumed 2026-10-26: the assembly module, emitted and elaborated against stubs

assembly.py reads lean/cert/rung3_plan2.json alone and emits Assembly.lean and Separation.lean on top of the committed module lean/Rung3Assembly/Support.lean. The two emitted files are written to a work directory, not committed. Assembly.lean imports the site roots scripts/69 emits, which are not module files, and Lean refuses that import from a module ("cannot import non-module ... from module", tried here); the tree requires every committed .lean file to be a module (tests/test_palomar_module_port.py, which failed CI on the first push of these files). pilot.json pins their SHA-256 (assembly_emitted). The hunt test checks that the text is byte-stable and matches the pin, that it imports each of the 215 site roots C<id> once, and that it uses each upper_<id> once, each grid point's beta_<id> once at a corner of its edge and twice inside, and centre_lt_centre once.

How the module argues, every step a lemma of lean/ZetaLean or of Support.lean:

  1. The 110 big boxes tile the four edges of the big square (half-width 7/32) end to end; the emitter asserts it. hseg_of_box / vseg_of_box turn each upper_<id> into ‖DH‖ ≤ 25499/10000 on its piece, icc_merge chains the pieces, and frontier_cover (through frontier_dhSquare) gives the bound on the big square's frontier.
  2. deriv_bound applies norm_deriv_DH_le to that bound and weakens M / (w2 - w) to L = 16 on the closed small square.
  3. The 104 grid points span each edge of the small square (half-width 3/64) corner to corner. Each pair of neighbours closes one cell by hcell / vcell, which wrap DH_lower_on_hcell / DH_lower_on_vcell. Each beta_<id> enters as le_trans (by norm_num) beta_<id>, so Lean reads β off the site theorem and norm_num checks it against the cell's need eps' + L h / 2. The emitter needs no β literal.
  4. centre_lt_centre and the lower bound on the small frontier feed dh_zero_of_square, which gives dh_zero_located (a zero of DH with real part in [808517/1000000 - 3/64, 808517/1000000 + 3/64] and imaginary part within 3/64 of 21424837/250000), and dh_statement_of_square, which gives theorem davenport_heilbronn : davenport_heilbronn_statement.
  5. dh_separation_of pairs that zero (in the open strip, off the line, at height at most
  6. with RH to height 100 for Mathlib's riemannZeta, taken as a hypothesis whose text is the body of hunt #137's RHUpToHeight 100. Separation.lean discharges the hypothesis with RHHeight100.rh_height_hundred.

dh_zero_of_square and dh_statement_of_square restate the square criterion at lean/ZetaLean/DHZeroCriterion.lean, line 185, with the same proof. The criterion's name carries the word this directory may not use, so no file here names it; this was the coordinator's decision of 2026-10-26. The hunt test scans every file here for that word, .lean included. The location corollary is stated with the plan's exact square. The task's rounded figures do not follow from it: Re s >= 0.7619 is above the square's left edge 0.761642, and |Im s - 85.699| <= 3/64 ignores the centre's 0.000348.

Elaborated, measured. On the shared box, one process at a time: Support.lean (as a module) compiled to an olean, with no errors. Then the emitted body (the text of Assembly.lean after its import lines) was compiled under a harness, with B_right_06 taken from its compiled 2026-10-21 oleans and the other 214 sites as stub interfaces. A stub is the site's SI_<id> or z_<id> as scripts/69 emits it, plus its theorem as an axiom with the generator's statement (each β at its cell need). Result: exit 0, no errors, 124.1 CPU-s, peak RSS 6.8 GiB. davenport_heilbronn, dh_zero_located' and dh_separation_of each print exactly propext, Classical.choice, Quot.sound and the 214 stubs, and never upper_B_right_06, whose real theorem was used (assembly_harness in pilot.json). This shows that the assembly's logic closes over the site statements. It does not prove davenport_heilbronn, which needs the 214 real sites.

Untested, precisely:

9. Follow-up 2026-10-26: the pilot site under --tower whole, and production again

The mode. scripts/60 ... --tower whole (backend scripts/69, tower_mode="whole") proves each prime tower by one decide +kernel of dirichletTermBallB ... = Sq_10 plus contains_dirichletTermBallB, as in section 7. It exports the names the staged fragment does, so composites, terms and the root are unchanged. The default stays staged and is byte-identical (RUNS.md). The generator refuses to emit a whole-mode tower whose literal differs in any field from the staged chain's last ball. Generating all 215 sites in whole mode (the census below) passed that check for all 15,608 towers.

B_right_06 end to end, measured. One process on the shared box, nice -n 10, generated by scripts/60 --arith ball --shard --tower whole --only B_right_06 and compiled by scripts/72 --jobs 1 --module-jobs 1 (whole_site in pilot.json):

moduleCPU-swall speak RSS GiBolean bytes
CB_right_06/Base.lean12.613.46.30124,152
CB_right_06/Primes.lean (23 towers)38.641.26.48481,672
CB_right_06/Composites.lean44.918.76.496,976,512
CB_right_06/Terms.lean43.719.46.495,369,688
CB_right_06.lean23.818.66.413,806,416
whole mode, 5 modules163.6111.36.4916,758,440
staged, 2026-10-21, 10 modules1,861.41,347.57.83179,767,088

So the site costs 11 times less CPU and leaves 11 times fewer olean bytes. In situ, the 23 towers cost 1.19 CPU-s each net of the 11.1 CPU-s import, less than the 3.4 to 5.3 of the standalone files in section 7, which each paid for their own declarations. The composites, terms and root now carry most of the site's cost; their rates here (0.70 CPU-s per composite, 0.48 per nonzero term, 0.15 per series term) match the 2026-10-21 in-situ rates.

The kernel refused both planted faults. Each fault is one tower literal one unit of 2^-64 off: Prime_2's Sq_10 radius low, and Prime_89's real centre high. Each was recompiled alone against the clean site's Base. Each was refused at that tower's ev_whole, and the power lemma that uses it (hp_B_right_06_2, hp_B_right_06_89) then failed to match.

Import probe. import CB_right_06 peaked 11.7 MB above import ZetaLean.BallCertSupport (6.73 GB) for 16.8 MB of site oleans: 0.70 bytes of RSS per olean byte, against 0.66 measured on the staged site.

Re-projection (whole_projection in pilot.json). This is an extrapolation from one site, hedged here at its point of use. A census emitted every site in whole mode in memory. All 215 come to 5 modules each (1,075 in all), with 0.19 GB of sources. Per site, CPU = modules x 11.1 + towers x 1.19 + composites x 0.70 + 4K x 0.48 + 5K x 0.15 CPU-s; the model gives B_right_06 162.1 against the 163.6 measured. The oleans are scaled from the pilot site by towers, composites and K, or by source byte.

staged (section 4)whole mode
CPU, 215 sites416 core-hours29.8 core-hours
largest sitethe centre, 31,817 CPU-sthe centre, 2,173 CPU-s (next: 734)
oleans, 215 sites123 to 146 GB14.8 to 16.0 GB (centre at most 0.37 GB)
assembly import, RSS above the support modules81 to 96 GB10.3 to 11.1 GB, on top of 6.7 GB
peak RSS per compile process7.8 GiB6.5 GiB

Where production fits (assessed against these projections, not tested):

10. Calibration on a GitHub runner, 2026-10-26

Measured on one ubuntu-latest runner (4 vCPU, 16 GB) by rung3-production.yml in calibrate mode, run 40340279740 at commit c697df4f0, which succeeded (calibration_runner in pilot.json). Both sites compiled under --tower whole at one Lean process. Both site theorems print exactly propext, Classical.choice and Quot.sound, and the verdict job counted the run.

siteCPU-swall speak RSS GBolean bytesprojected CPU-s (this box)runner / projected
centre (K = 361)1,348.1556.611.32343,533,3522,173.20.620
B_right_06 (K = 17)65.939.77.2016,758,408162.10.407

The job took 928 s in all, of which 295 s was setup. That setup was 119 s to free disk, 91 s for lake exe cache get into a fresh lean/.lake (the archive cache was empty on this first run), 1 s of preflight, 50 s to build the ZetaLean closure and uploads. Generation took 28 s, the compile 596 s and the axiom audit 9 s.

What changes:

The full plan, re-sized: 20 jobs as before, assigned by projected CPU. Each job is bounded by setup plus 1.5 times its runner-scaled CPU at one core: 1.43 to 1.49 hours, against the 4-hour target and the 350-minute limit. At 6 jobs at once, the 21 jobs (20 plus the fault job) run in 4 waves.

11. A miss: site roots could not be imported together, and the fix

What was wrong. While writing the assembly runbook, an import of two runner-built site roots (import Ccentre and import CB_right_06, from calibration run 40340279740) failed: environment already contains 'ZetaLean.DH.Cert.Prime_79.S' from Ccentre.Primes. scripts/69 put every prime tower's public declarations in ZetaLean.DH.Cert.Prime_<p>, which is not qualified by site, so any two sites sharing a prime declare the same names. The assembly imports all 215 roots in one environment, so it could not have elaborated with that output in either tower mode. The harness of section 8 used one real site and stubs for the rest, so it never imported two real towers together. That was this lane's miss, and the full run started at 14:50 was cancelled for it (RUNS.md).

The fix. In --tower whole only, the tower namespace is now Prime_<sid>_<p> (scripts/69 tower_namespace). The staged default is unchanged and still byte-identical: the 94 files of B_right_06 and the centre hash equal before and after. The staged default therefore keeps the collision, recorded below.

Checked, measured on this box:

The census of names (namespace_census). All 215 sites emitted in whole mode declare 532,652 public names, and none repeats. Under the staged default, each of the 15,608 towers declares the same 79 public names in Prime_<p>, so 1,211,070 repeated declarations would collide (8,216 distinct names). Prime 2's tower alone appears in all 215 sites. That is the defect the staged default keeps, by the ruling that it stay byte-identical.

12. Calibration again, with the renamed towers (attempt 2)

Measured, run 40350287309 at commit 4bac8d79d, the same request with "attempt": 2, which succeeded (calibration_runner; section 10's run is kept as calibration_runner_v1). Both sites compiled with the site-qualified tower namespaces, and both site theorems print exactly propext, Classical.choice and Quot.sound.

siteCPU-swall speak RSS GBolean bytesprojected CPU-s (this box)runner / projected
centre (K = 361)1,229.1533.811.31343,535,4082,173.20.566
B_right_06 (K = 17)58.137.67.1916,758,616162.10.358

The job took 909 s in all, of which 308 s was setup: 146 s to free disk, 71 s for the cache fetch (from the archive cache saved by the first run), 2 s of preflight and 43 s for the ZetaLean build. The results match attempt 1 within runner variation (centre CPU 9 percent lower, olean bytes equal to within 2.1 kB, the same peak RSS). The rename cost nothing measurable.

Full plan, re-sized from attempt 2: 20 jobs, one Lean process each (11.31 GB peak). Every job is bounded at 1.32 to 1.37 hours, setup included. On runners that comes to about 16.9 core-hours, scaled by the larger ratio.

13. The full run's fault job left no verdict, and the fix

In full run 40353536947, the job sites fault (full) failed (job 121679942724, 17:16:52 to 17:21:29). The coordinator read its log. The faulted compile failed as intended: CB_right_06/Primes.lean failed after 15.3 s and Base.lean compiled. But the step had no shell: key, so GitHub ran it as bash -e {0}. The step's set -uo pipefail left that -e in force, so the failing compile pipeline ended the step before rc=${PIPESTATUS[0]} and production.py refused ran. No fault.json was written. The per-module logs that name the refusing declaration were not uploaded either, because the oleans upload skips the fault job. The run's fault evidence is therefore void, not negative: Primes.lean was rejected, but the record does not say where. The 20 site jobs are unaffected: their compile steps fail closed, as intended.

The fix:

Then only the fault is re-run, in a new run ("mode": "fault", "completes_run": 40353536947), with no production site regenerated or uploaded again. The verdict pairs the two runs: production.py verdict --runs 40353536947 <fault run> takes the sites and axioms from the first and the fault from the second, and collect --run takes several runs the same way.

14. Production, and the theorem

The production record is RESULTS.md; its numbers come from the production section of pilot.json. In brief: 215/215 sites compiled on GitHub Actions under --tower whole in 16.1 runner core-hours. The oleans total 15.12 GB, inside the projected 14.8 to 16.0. The assembly peaked at 17.87 GB RSS, inside the projected 17.0 to 17.9. theorem davenport_heilbronn : davenport_heilbronn_statement and the located zero rest on the three standard axioms.