28 KiB
F10. The ladder's signal: monotonicity, the 89 percent case, the down-step, the memoisation
Attack-pass row F10 (docs/plans/cryptanalysis.md section 4.2; the pass record docs/analysis/attack-pass-2026-10.md).
7 October 2026, 09:05 to 10:30 UK. Sub-agent F10 on branch attack-pass (worktree igneum-wt-attack, HEAD 8e36faf6 at
the start of the work; the brief named 924288d1, the branch had moved on). Files: tools/attack/f10-ladder/ and this
record. Nothing under vendor/, infra/ or the node was edited.
1. Target
The ladder as PROPOSED on branch ladder (repo tip 7003f9f5, 6 October 2026 23:58 UK; also release-0.3.18), node
fork ladder-node tip 1591ee1d (vendor/igneum-node-ladder), docs/design/latency-ladder.md sections 3, 5a, 9 and 11.
| Item | Value at the commit run |
|---|---|
| The N ladder (counted ops) | 102,100; 132,100; 199,600; 330,700; 649,400; 1,001,600 (reps 27, 35, 53, 88, 173, 267; the design doc's round figures 100,000; 130,000; 200,000; 330,000; 650,000; 1,000,000) |
| Floor | rung 0, reps 27 = class v4 byte for byte (V4_CLASS) |
| Admissible rungs in the file run | 0, 1, 2 (rungs 3 to 5 admissible: false, the verifier table of section 5) |
| The step rule | igneum::latency_ladder_step_signalled (consensus/core/src/igneum.rs lines 664 to 687): up one rung when all 7 windows have at least 9,000 bps of blue blocks up, the rung above is admissible, and the oldest window begins at or after the DAA score where the current step took effect; down one rung by the same test on the down bit, never below 0; otherwise the state stands |
| The carrier | header version bit 15 = up, bit 14 = down, both or neither = none (ladder_signal_of); the object byte keeps bits 8 to 13; the low byte is the block version |
| The windows | 7 consecutive windows of latency_ladder_window_daa (86,400 DAA on mainnet, 120 on the 60x profile, 100 in the exact-share runs here) ending at the epoch's seed block; one walk of the seed block's blue past (class_signal::tally_window_by); share per window = floor(10,000 x signalling / total) |
| The seed block of epoch e | the last selected-chain block with DAA score strictly below L e - lead (class_signal::seed_below) |
| The memo | processes::latency_ladder::step_of_epoch: a static HashMap<seed hash, LadderDecision>, filled by walking earlier epochs' seed blocks down the selected chain to a memoised decision or the activation epoch, cleared when it passes 100,000 entries; empty at every process start |
The box binaries used, read-only: /srv/builds/igneum-wt-ladder/vendor/igneum-node-ladder/target/release/{igneumd,igneum-miner}
(built 6 October 2026 22:15Z, the fork checkout at 1591ee1d = the ladder-node tip, one untracked file
.build-remote-sha-target) and /srv/builds/igneum-wt-ladder/igneum-pow/target/release/igneum-pow (built 22:38Z, after
6b30e85). The igneumd binary's embedded commit string is 8dbb7a23, the PARENT of the ladder commit, not 1591ee1d
(strings finds 8dbb7a23 twice and 1591ee1d never); the ladder code is in it (the step line
Latency ladder step by miner signal is present, and every run below stepped). This is the stale-commit-string class
of tools/ci/commit-string-check.sh (CLAUDE.md, 6 October 2026); it does not touch the result, and it is reported to
main in section 8.
2. Known-failed shape and gate
Known-failed shape: a chip owner stepping the ladder down (cheaper N) without the 90 percent threshold, or a step registered under 90 percent in either direction. Gate (plan 4.2 row F10, the same as 1.4): no step without 90 percent over 7 windows in either direction; a step down needs the same. What a failure moves: the step rule's text in spec 01 before the ladder is frozen.
3. Method
Two instruments, both run on igneum-build-1 on the F10 cores (nice -n 10 taskset -c 38-39,86-87), each run under a
SHARED hold of the box measure file for the run only (every run capped under 30 minutes by its own --secs), on ports
29900 and up, devnet suffix 990, data /tmp/igneum-fast-time-attack-f10, so nothing collides with the ladder lane's
network (29720, 972) or F7's (29800, 980). Scripts and copies: tools/attack/f10-ladder/ (box mirror
/srv/builds/igneum-wt-attack/attack-f10/, run logs under runs/).
| Instrument | File | What it is |
|---|---|---|
| The ladder lane's harness, verbatim | tools/attack/f10-ladder/latency-ladder.mjs |
infra/fast-time/latency-ladder.mjs from ladder at 7003f9f5, unchanged except the root lookup, this directory's copy of the ladder branch's override-60x.json (the attack-pass tree's copy lacks the latency_ladder fields), and the F10 ports, suffix, data dir and binary paths. Three nodes, three real CPU miners (one thread each), class v4 from genesis, the ladder active from DAA 0, windows of 60 DAA. Trusted only after it fires on the known-failed case (--signal up,up,none --expect step must report FAIL) and the known pass (--signal up,up,up --expect step) |
| The exact-share driver, new | tools/attack/f10-ladder/ladder-exact.mjs |
Three nodes on the same fork with skip_proof_of_work; ONE producer takes node 0's template, writes the ladder bits it wants into the header version and submits the block, one block per DAA score on a linear chain, so every window of W = 100 DAA holds exactly 100 blue blocks, one of each residue modulo 100. A schedule names per DAA range the direction and how many residues carry no signal: 11 residues give 8,900 bps in every window whatever the window's alignment, 10 give 9,000. "None" blocks alternate between no bits and both bits, so the chain shows both forms read as none. The driver polls every node's template (rung, weakest up, weakest down) through the run, restarts a node mid-window on request (SIGINT, same data dir, same arguments), and at the end re-tallies the chain in JavaScript (an independent copy of the rule: the seed rule, the 7 buckets, floor rounding, admissibility, the cool-down) and compares it with what the nodes did |
Why the second instrument: three equal miners cast 0, 33, 67 or 100 percent, and a real miner's share in any one
window scatters by several points (the lane's own runs: 5,833 to 6,333 bps weakest for a 67 percent population), so no
real-mining run can hold 8,900 to 8,999 bps in the weakest of seven windows. The rule is consensus-side and reads the
chain's headers, not the miner, so a chain whose headers carry exact shares asks it the exact question. The skip-PoW
network accepts every submitted block (each node logs PoW rejected ... by igneum-lottery-v2-bound (daa N, nonce 0x0) at
INFO and accepts the block; the chain-side fact is the block count on every node).
The arithmetic of the exact-share cases (L = 60 DAA per epoch, lead 10, W = 100, 7 W = 700; genesis and the first produced block both sit at DAA 0, then one block per DAA): the seed block of epoch e is at DAA 60 e - 11; the seven windows are full from epoch 12 (seed 709); the oldest window of epoch e is DAA [60 e - 710, 60 e - 611]; after a step that took effect at DAA S the next decision is the first epoch with 60 e - 710 >= S.
| Case | Schedule (from DAA : direction : residues with no signal) | Expected by hand | Why |
|---|---|---|---|
| eighty-nine | 0🆙11, 1200🆙10 | no step through epoch 30 at a weakest of 8,900; rung 1 at epoch 31 when the weakest first reads 9,000; rung 2 at epoch 43, the first epoch after the cool-down; nothing else to epoch 45 | residue 10 turns from none to up at DAA 1,200; the oldest window's residue-10 block is 1,210 at epoch 31 (1,110 at epoch 30); after the step at DAA 1,860 the first epoch with 60 e - 710 >= 1,860 is 43 |
| down | 0🆙0, 720:down:11, 1500:down:10, node restarts n2 at DAA 1,000, n1 at 2,300, n2 at 2,700 | rung 1 at epoch 12 (100 percent up); no step down at 8,900 down (epochs 24 to 35, the first cooled-down epoch is 24); rung 0 at epoch 36 when the weakest down first reads 9,000; then down at 9,000 through epoch 50 with no step below 0 (epoch 48 is the first cooled-down epoch after the down-step and the rule must hold at rung 0) | the oldest window's residue-10 block is 1,510 at epoch 36 (1,410 at epoch 35); after the down-step at DAA 2,160 the first epoch with 60 e - 710 >= 2,160 is 48 |
| floor | 0:down:0 | no step at all through epoch 20 | 100 percent down at rung 0 from genesis: the windows are full from epoch 12, the cool-down is trivially met, the rule must stand at 0 |
4. Runs
All on igneum-build-1, 7 October 2026. Times UK (UTC+1); the logs are UTC. Every run held the measure file
shared for its own length only; the first waited behind F6's exclusive hold (its batch A, 09:15 to 09:25 UK). Log paths
are under /srv/builds/igneum-wt-attack/attack-f10/runs/ on the box, copied to tools/attack/f10-ladder/runs/ here
(<name>.log = harness stdout, <name>.json = summary, <name>-n{0,1,2}.log = node logs).
4.1 The harness, trusted: the known-failed case and the known pass (real CPU mining, W = 60 DAA)
| Case | Run (UK) | Result | Numbers | Files |
|---|---|---|---|---|
Known-failed, --signal up,up,none --expect step |
09:25:51 to 09:36:50 | FAIL rc=1, as it must: no step | no step over epochs 0 to 10; weakest-of-seven up share at the sink 5,833 bps from epoch 7 (5,500 at epoch 10); on the chain 385 blocks up, 221 none (6,353 bps up); 606 blocks; 0 rejected; one sink 4a7f20cc at 605/605/605; the 8 step checks failed (template_stepped_to_rung_1 ... rung1_ids_differ_from_the_same_seed_rung0_id); the lane's genesis low-byte fault did not fire (fixed in the file) | baseline-fail.log, .json |
Known pass, --signal up,up,up --expect step |
09:36:50 to 09:48:00 | PASS 18 of 18 | step line on 3 of 3 nodes at epoch 8: 420 of 420 blue blocks up, weakest up 10,000 bps, shares [10000 x 7]; template rung 1 (35 passes) from epoch 8 (DAA 480) at 538.2 s; epochs 9 and 10 at rung 1, one step line per node (no second step inside seven windows); 481 / 132 blocks across the boundary; 612 blocks up and genesis none (9,984 bps); 0 rejected; one sink 41e81944 at 612/612/612; the miners' rung-1 ids on epochs 8, 9, 10 equal the CLI's --shadow-reps 35 id and differ from rung 0 (e8 218fa530b4c599b0 against 5c5a326a31a4795d, e9 8f30ce6666b4ea8f against c73f3c63daac3748, e10 e2ea0a1ea8b4ca44 against 626455372164a1b5) |
baseline-pass.log, .json |
Both reproduce the ladder lane's runs of 6 October (docs/design/latency-ladder-harness/), on the F10 cores.
4.2 The exact-share cases (skip-PoW, one block per DAA, W = 100 DAA, 8 blocks per second)
| Case | Run (UK) | Harness line | What the chain did | Files |
|---|---|---|---|---|
| eighty-nine (first run, driver v1) | 09:48:00 to 09:54:27 | FAIL rc=1 on three harness faults (section 4.3); the chain's facts are those of the re-run | identical to the re-run below | exact-89.log, .json |
| eighty-nine (re-run, driver v2) | 10:04:45 to 10:11:14 | PASS 19 of 19 | 2,701 blocks, linear; 2,418 up, 283 none (135 of them with both bits); weakest up 8,900 bps at every epoch 12 to 30 and NO step (19 epochs, "stands" on every node); epoch 31: weakest 9,000 exactly, step line on 3 of 3: 630 of 700 blue blocks up, shares [9000 x 7], rung 1 (35 passes); epochs 32 to 42 at 9,000 with no step (cool-down: the oldest window begins 1,210 to 1,810, the step took effect at 1,860); epoch 43: rung 2 (53 passes), 630 of 700; 44 and 45 cool-down; 0 disagreements between nodes at any poll; one sink 1dd776b4 at 2700/2700/2700; 2 step lines per node; 382 s |
exact-89b.log, .json, -n0.log |
| floor (driver v2) | 10:01:40 to 10:04:38 | PASS 19 of 19 | 1,201 blocks; 1,200 down, genesis none; from epoch 12 every window reads 10,000 bps down at rung 0; the rule stands on every node for epochs 12 to 20 ("down signalled at rung 0: the floor"); no step line on any node; one sink a52e6a71 at 1200/1200/1200 | exact-floor.log, .json |
| down (first run, driver v1) | 09:54:27 to 10:01:40 | FAIL rc=1 on the same three harness faults | identical to the third run below, restarts included | exact-down.log, .json, -n1.log, -n2.log |
| down (second run, driver v2) | 10:11:14 to 10:18:26 | FAIL rc=1 on one harness fault (the anchor comparison at the two boundary epochs 13 and 23, section 4.3); 17 comparable epochs equal; the step lines' own weakest equal the oracle | identical to the third run | exact-downb.log, .json, -n{0,1,2}.log |
| down (third run, driver v3) | 10:19:13 to 10:26:25 | PASS 19 of 19 | 3,001 blocks, linear; 720 up, 2,053 down, 228 none (110 with both bits); epoch 12: rung 1 on 3 of 3 (700 of 700 blue blocks up, weakest up 10,000); epochs 13 to 23 cool-down (the oldest window begins 70 to 670, the step took effect at 720); epochs 24 to 35: weakest down 8,900 bps on every node, NO step down (12 epochs "stands"); epoch 36: weakest down 9,000 exactly, step line on 3 of 3: 0 of 700 blue blocks up, 630 down, rung 0 (27 passes, from rung 1); epochs 37 to 47 cool-down; epochs 48 to 50: 9,000 down at rung 0, the rule stands (never below 0), no third step line; restarts: n2 at DAA 1,004 (1 step line before, 4 after), n1 at DAA 2,304 (2 before, 2 after), n2 at DAA 2,704 (3 before, 2 after), every line after a restart identical in epoch, rung, origin and weakest to the lines before; 0 disagreements; one sink 20c6b367 at 3000/3000/3000; step lines 2 / 4 / 5 per node; 425 s |
exact-downc.log, .json, -n{0,1,2}.log |
Per epoch, the down case as the nodes and the oracle saw it (from exact-downc.json; "rungs" = the first template of the
epoch on n0 / n1 / n2; "weakest" = the decision's number from the step line where one exists, else the template's live
sink tally, which equals the seed-anchored oracle at every epoch with no schedule boundary inside the windows):
| Epoch | Seed DAA | Rungs n0/n1/n2 | Weakest up / down (bps) | Oracle rung | Oracle reason |
|---|---|---|---|---|---|
| 11 | 649 | 0/0/0 | partial | 0 | windows not full |
| 12 | 709 | 1/1/1 | 10,000 / 0 | 1 | up: 700 of 700 |
| 13 to 23 | 769 to 1,369 | 1/1/1 | mixed, under 9,000 both ways | 1 | cool-down (oldest window begins before 720) |
| 24 to 35 | 1,429 to 2,089 | 1/1/1 | 0 / 8,900 | 1 | stands: 8,900 is under 9,000 |
| 36 | 2,149 | 0/0/0 | 0 / 9,000 | 0 | down: 630 of 700 |
| 37 to 47 | 2,209 to 2,809 | 0/0/0 | 0 / 9,000 | 0 | cool-down (oldest window begins before 2,160) |
| 48 to 50 | 2,869 to 2,989 | 0/0/0 | 0 / 9,000 | 0 | down signalled at rung 0: the floor |
And the eighty-nine case (exact-89b.json):
| Epoch | Seed DAA | Rungs n0/n1/n2 | Weakest up (bps) | Oracle rung | Oracle reason |
|---|---|---|---|---|---|
| 12 to 30 | 709 to 1,789 | 0/0/0 | 8,900 | 0 | stands, 19 epochs |
| 31 | 1,849 | 1/1/1 | 9,000 | 1 | up: 630 of 700 |
| 32 to 42 | 1,909 to 2,509 | 1/1/1 | 9,000 | 1 | cool-down (oldest window begins 1,210 to 1,810, the step took effect at 1,860) |
| 43 | 2,569 | 2/2/2 | 9,000 | 2 | up: 630 of 700 |
| 44 to 45 | 2,629 to 2,689 | 2/2/2 | 9,000 | 2 | cool-down |
4.3 Harness faults found and fixed on the way (the driver's, never the chain's)
| Fault | Seen | Fix |
|---|---|---|
every_produced_block_on_every_node compared blockCount with produced + 1; the node's blockCount excludes genesis |
exact-89 first run, 09:54 UK | compare with produced (2,700 = 2,700) |
zero_rejected_by_nodes grepped ban and matched the finality parameter line ... ban 120 ... |
same run | the word dropped; the skip-PoW INFO line PoW rejected ... by igneum-lottery-v2-bound excluded by its own text |
node_weakest_equals_oracle_weakest compared the template's weakest with the seed-anchored oracle at every epoch; the template's number is the LIVE tally anchored at the sink (consensus/mod.rs get_pow_epoch_info, tally_ladder(..., sink, ...)), read at the epoch's first template, sink = seed + lead (10 DAA) |
epoch 11 of exact-89 (49 of 59 at the sink against 39 of 49 at the seed); epochs 13 and 23 of the second down run (40 up in (679, 779] against 50 in (669, 769], the boundary at 720 inside both) | compared only at epochs with seven full windows and no schedule boundary inside the windows plus the lead; a new check compares the decision's own weakest (the step line) with the oracle at every stepped epoch, which passed in every run |
The smoke run (smoke.log, 09:14 UK, 3 epochs) validated the template round trip (submitBlock reports
{"type":"success"}, 180 blocks on 3 of 3 nodes at 8 per second).
5. What the runs show against the gate
| Gate clause | Shown by | Numbers |
|---|---|---|
| No step up without 90 percent over 7 windows | eighty-nine: 19 epochs at 8,900 bps in every window, rung 0 held on every node; the step came at the first epoch whose weakest read 9,000, 630 of 700 blue blocks | epochs 12 to 30 stand; 31 steps |
| No step down without 90 percent over 7 windows | down: 12 cooled-down epochs at 8,900 bps down in every window, rung 1 held on every node; the step down came at the first epoch whose weakest down read 9,000, 630 of 700 | epochs 24 to 35 stand; 36 steps |
| A step down needs the same cool-down | down: epochs 13 to 23 at rung 1 with the oldest window beginning before the step took effect: the rule stood although the up share had collapsed | 11 epochs |
| Never below 0 | floor: 10,000 bps down at rung 0 for 9 epochs, no step line; down: 9,000 bps down at rung 0 for epochs 48 to 50 after the cool-down, no step line | 12 epochs across two runs |
| Monotone: one rung per decision, seven windows between decisions | eighty-nine: rung 1 at 31, rung 2 not before 43 with 9,000 in every window throughout; down: rung 1 at 12, rung 0 at 36 | the cool-down held 11 epochs each time |
| The decision computed once per seed block and reused | one or two step lines per process per stepped epoch (two when the first template and header processing walked concurrently), none afterwards | n0: 2 lines for 2 steps in every exact run |
| A node restarted mid-window reaches the same decision | three restarts in the down case: every step line after a restart repeats the lines before it in epoch, rung, origin and weakest; the restarted node's template rung equals the others' at every epoch | n2 at 1,004 and 2,704, n1 at 2,304 |
| Two nodes never disagree on the rung at the same height | 0 disagreements at every observation (every fifth block) and at every epoch's first template, in every run | 5 exact runs, 2 baseline runs |
| Both bits = none | 135 and 110 both-bits blocks counted as none by the oracle and by the nodes (the shares matched) | eighty-nine, down |
| The known-failed shape (a chip owner stepping down under 90 percent; a step registered under 90 percent) | did not occur; 8,900 held in both directions, floor rounding puts 8,999 below the line (unit test, igneum.rs 1161) |
gate holds |
6. Static reading of the rule (what the harness cannot show)
Read in the fork at 1591ee1d before the runs. Each line is a property of the code as written, with the place.
| Property | Where | Reading |
|---|---|---|
| Symmetry of the two directions | igneum.rs 676 to 686 |
one closure all(shares) serves both bits; the up branch runs first, then all(down) && previous.step > 0; up and down cannot both reach 9,000 bps of one window's blocks, so the order never decides |
| The cool-down is direction-free | igneum.rs 674 |
first_counted_daa < previous.since_daa returns the previous state before either branch is read; a step down waits the same seven windows after a step up as a step up does after a step down |
| Never below 0 | igneum.rs 681 |
previous.step > 0 guards the subtraction; a 100 percent down signal at rung 0 stands (the floor case below shows it on the chain) |
| Never past an inadmissible rung | igneum.rs 679 |
ladder.admissible(previous.step + 1); rung 3 is admissible: false in the file, so from rung 2 a 100 percent up signal stands (unit test latency_ladder_rule, igneum.rs 1161) |
| Floor rounding | igneum.rs 431 to 437 |
signal_share_bps = floor(10,000 x signalling / total); 89 of 100 blue blocks is 8,900, 90 is 9,000; on a mainnet window of 86,400 blocks 77,759 up is 8,999 and 77,760 is 9,000 |
| Both bits set | igneum.rs 639 to 645 |
version & 0xc000 == 0xc000 falls to None; a header cannot vote both ways and cannot vote twice |
| Weakest of seven | class_signal.rs SignalTally::weakest_bps and the rule's all |
the decision rests on the lowest of the seven windows; one bought window at 100 percent moves nothing (unit test "one bought day does not move it") |
| The windows are the seed block's own past | class_signal.rs tally_window_by |
the anchor and the mergeset blues of each selected-chain block walking down, bucketed by daa_c - daa, stopping once daa_cur + merge_depth < window_start; blocks above the seed are never counted, so the seven windows are fixed once the seed block is |
| The memo is sound | latency_ladder.rs step_of_epoch |
keyed by the seed block's hash; the decision is a function of that block's selected-chain past and of process-global constants installed from the file (ladder, activation, window), so two processes with the same file and the same chain compute the same value; the memo is never read across a param change because the params are fixed at start; cleared above 100,000 entries, then rebuilt by the walk |
| Concurrent first computation | latency_ladder.rs memo_get / memo_put |
the lock is not held across the walk, so two concurrent callers may both walk and both log the step line; both write the same value, so the chain's decision is unaffected (the runs below show one or two step lines per process for the same epoch, identical in content) |
| A node without the history | latency_ladder.rs step_of_epoch, the two warn! returns |
a node whose seed block's windows cannot be walked (synced from a pruning proof) decides RUNG 0 and logs "a ladder witness is owed". After a step up, such a node runs rung 0's program and refuses rung 1's blocks: a split between full-history nodes and proof-synced nodes. The design doc lists the witness as owed (section 9). This is not a fault of the step rule and the harness cannot reach it (every node here has the history); it is a precondition on activation: no network activates the ladder while any peer syncs from a proof without the witness. Routed to main in section 8 |
Nothing in the reading admits a step under 9,000 bps in either direction, a step down under the cool-down, a step below rung 0, or a decision that depends on which node computes it or when.
7. Consequences per tier
The rule holds, so a step in either direction costs 90 percent of blue blocks in each of seven consecutive days, and
the earliest second step is seven days after the first. What a WRONGFUL step would have done, had the rule admitted one
under 90 percent, is the measured per-rung table of docs/design/latency-ladder.md section 8 (algorithm.md 5.3a rungs,
igneum-build-1 verifier) read in each direction. Every row below is that table's number, not a new measurement.
| Wrongful step | M5 Max (Apple tier) | RTX 5090 at 431 W | RTX 4070 at 160 W | RX 9070 XT | 8 / 12 / 16 GB cards, rigs, pools | Verifier (half-core) | f = 1 chip's per-joule edge over the 5090 |
|---|---|---|---|---|---|---|---|
| Up 0 to 1 (102,100 to 132,100 ops) under 90 percent | -3.3 points of rate, 0 W more | 0 | 0 | 0 | 0 (the shadow costs ALU, not memory; the dataset size is the schedule's, not the ladder's) | +0.2 ms | 2.1x to 1.7x at k = 1 (3.9x to 3.4x at k about 0.33) |
| Up 1 to 2 (to 199,600) under 90 percent | -6 more points | -2.7 percent | +21 W | 0 | 0 | +0.5 ms | to 1.3x (2.8x) |
| Up 2 to 3 (to 330,700): inadmissible, never entered | -21 percent | -35 percent (compute-bound at the cap) | -12 percent | +3.6 percent | 0 | +0.9 ms | 3.0x at k about 0.33 |
| Down 2 to 1, 1 to 0 under 90 percent (the chip owner's step) | the Apple tier gets its 6 then 3.3 points back | +2.7 percent then 0 | -21 W then 0 | 0 | 0 | -0.5 then -0.2 ms | the chip regains 1.3x to 1.7x to 2.1x (2.8x to 3.4x to 3.9x): every rung down hands the stored-dataset chip back the edge the miners paid for |
Reading per tier, with the rule as it stands:
| Tier | What the result means |
|---|---|
| Home card, 8 / 12 / 16 / 24 GB, any vendor, any OS | A step up costs rate only on the Apple tier at rungs 1 and 2, and on NVIDIA from rung 2; no step happens unless 90 percent of blocks over seven days ask for it, so a minority that would lose rate cannot be moved by a bought day or a 89 percent week, and a chip owner under 90 percent cannot move the rung down to cheapen its core. A 90 percent majority can step the chain down one rung per week to the floor (rung 0 = class v4 as it ships), which is the design's floor and not a weakness of the rule: at 90 percent of blocks the owner already orders the chain |
| Rig, pool user | The same; a pool signals per block through its node's IGNEUM_LADDER_SIGNAL (the app's toggle later), so a pool's share of blocks is its weight |
| Verifier (the node, the proof) | Admissibility is a genesis flag per rung; rung 3 is never entered by any signal until a quiet re-measurement before genesis moves the flag (section 4 of the design doc); the memo keeps the per-template cost to one walk per seed block per process |
| A node synced from a pruning proof | Decides rung 0 until the ladder witness lands (section 6, last row): the ladder must not activate on a network where such nodes exist before the witness. This is the one consequence the rule's text does not state and the spec line should |
8. Verdict, and what goes to main
PASS. No step without 90 percent of blue blocks in each of seven consecutive windows in either direction; a step down needs the same 90 percent and the same seven-window cool-down; the floor holds under 100 percent down; the decision is per seed block, memoised per process, recomputed identically after a restart, and never differs between nodes at the same epoch. The known-failed harness case fails, the known pass passes, and three new cases (89 percent up, 89 then 90 percent down with restarts, the floor) pass on the chain and on the harness's own 19 checks. The step rule's text in spec 01 needs no change for the gate.
To main, not findings against the gate:
| Item | What | Proposed route |
|---|---|---|
Stale commit string in the ladder lane's igneumd |
the binary built 6 October 22:15Z from the fork at 1591ee1d carries 8dbb7a23 (its parent) and no 1591ee1d; the ladder code is in it | the commit-string-check class (CLAUDE.md, 6 October 2026); the ladder lane rebuilds with the two-step before any Devnet 2 crossing; nothing in this row depends on it |
| Proof-synced nodes decide rung 0 until the witness lands | processes::latency_ladder::step_of_epoch returns rung 0 with a warning when the seed block's windows cannot be walked; after a step, such a node runs the wrong program and splits from full-history peers |
a precondition line for the step rule's text in spec 01 when the ladder is adopted: "the ladder activates only once every node can walk the seven windows below every seed block, or carries the ladder witness in its pruning proof"; the design doc already lists the witness as owed (section 9); node lane |
Spec text for the ladder, when adopted (none in spec 01 today; the only ladder there is epoch_len's) |
the rule as run: 90 percent of blue blocks in each of 7 consecutive windows ending at the seed block, floor rounding, one rung per decision, the oldest window at or after the last step in either direction, never below rung 0, never into an inadmissible rung; the template's weakest is the live sink tally and the decision's is at the seed | the algorithm lane's spec line; this record is the test it cites |
| Three harness faults in the F10 driver | section 4.3; all three were the driver's reading of the node, fixed in ladder-exact.mjs v3 |
none owed; recorded so the firm does not repeat them |
Blocked: nothing. Not run: a real-mining 89 percent case (three equal miners cannot cast it; the exact-share driver asks the rule the same question through the same submit path and the same consensus code).