|
4 | 4 |
|
5 | 5 | This is the executable plan derived from [`docs/`](docs/). The design documents say *what* to build and *why*; this says *in what order*, *with what acceptance test*, and *what changes because of the machine we're actually on*. |
6 | 6 |
|
7 | | -**Status:** M0–M22 complete; v5 landed, v6 running. The live path is wired but unspent (mock-driven throughout; the first live run awaits an API key). M12's code-generation half is blocked on both a key and Docker. Nothing below is claimed as done until its acceptance criteria are green in CI. |
| 7 | +**Status:** M0–M23 complete; v5 and v6 landed, v7 registered and not yet run. **V6's result does not adjudicate what v6 registered** — its treatment arm did not implement the mechanism it names; see M23. The live path is wired but unspent (mock-driven throughout; the first live run awaits an API key). M12's code-generation half is blocked on both a key and Docker. Nothing below is claimed as done until its acceptance criteria are green in CI. |
8 | 8 |
|
9 | 9 | --- |
10 | 10 |
|
@@ -464,7 +464,7 @@ It lands **under half the true value 8.9% of the time**. When it does, the escal |
464 | 464 |
|
465 | 465 | **A third instance of the same bug shape.** The paper's results-path table was a dict keyed by protocol version, and it raised a `KeyError` the moment v6 was registered. That is the third time something keyed by protocol version was maintained beside the registry instead of computed from it — after the CI job that listed protocols by hand, and the ladder that ran eight arms under a nine-arm plan. It is derived now. |
466 | 466 |
|
467 | | -All six protocols verify and rebuild identically. **v6 has not been run**: at roughly double B8's escalation it is a ~6-hour ladder, and the v5 run has to finish first. |
| 467 | +All seven protocols verify and rebuild identically. **v6 has since been run, and refuted — against an arm that never sized anything conservatively.** The upper bound described here was built correctly and measured correctly; what was missing was the wire from the arm to it. M23 has the count of what that cost. |
468 | 468 |
|
469 | 469 | --- |
470 | 470 |
|
@@ -671,6 +671,106 @@ guards it asserts the overlap still exists rather than asserting it away. |
671 | 671 |
|
672 | 672 | --- |
673 | 673 |
|
| 674 | +### M23 · The switch was declared, hashed, translated — and read by nothing ✅ |
| 675 | +The v6 ladder finished: twenty-two passes, ten arms, `benchmark/results.v6.lock.json`. |
| 676 | + |
| 677 | +``` |
| 678 | + PREDICTION REFUTED coverage (B9-B8) = +0.0333, 95% CI [-0.0111, +0.0889] |
| 679 | +``` |
| 680 | + |
| 681 | +**That refutation is not about conservative sizing.** B9 is B8 with one boolean, |
| 682 | +`conservative_escalation`, which sizes the escalation from an 80% chi-square upper bound on the |
| 683 | +noise instead of the point estimate. The field is declared on the arm, hashed into protocol v6, |
| 684 | +carried by `mechanisms_for` into the kernel's `Mechanisms`, read at `kernel.py`, and handed to |
| 685 | +`_replicate(conservative=…)`. **`_replicate` never used it, and neither call to `_escalate` |
| 686 | +passed it at all.** So the flag crossed every boundary and stopped one line short of the |
| 687 | +calculation. |
| 688 | + |
| 689 | +The signature is unmistakable once looked for: |
| 690 | + |
| 691 | +| | B8 | B9 | |
| 692 | +|---|---|---| |
| 693 | +| seed counts, 180 outcomes | 5×60, 6×6, 7×3, 8×3, 9×3, 11×6, 14×6, 18×6, 19×6, 21×6, 23×3, 24×72 | *identical* | |
| 694 | +| total seeds bought | 2,703 | 2,703 | |
| 695 | +| outcomes where the two differ | — | **0 of 180** | |
| 696 | + |
| 697 | +Not similar. Identical, item by item and replicate by replicate. After threading the flag |
| 698 | +through both call sites, the same two items move from 12 seeds to 21. |
| 699 | + |
| 700 | +**Three checks, and the one that was missing.** Each catches what the one above it cannot. |
| 701 | + |
| 702 | +1. `unread_switches()` (M22) flips a field and asks whether `Mechanisms` changes. It found |
| 703 | + `reviewer`. It cannot find this one — `conservative_escalation` *is* a field on `Mechanisms`, |
| 704 | + so flipping it does change the object. |
| 705 | +2. **A parameter accepted and never read.** `ARG` is now selected in ruff, and it names the bug |
| 706 | + in one line: `kernel.py:922 ARG002 Unused method argument: conservative`. This is the check |
| 707 | + that would have cost a second rather than a six-hour ladder. Five modules are exempted — |
| 708 | + `@detector`, `@register_view`, `@transform_op`, SQLAlchemy's `TypeDecorator` and its event |
| 709 | + listeners, where the signature *is* the interface — and `kernel.py` deliberately is not. |
| 710 | + `tests/test_switches.py` asserts the same rule over the kernel by AST, so no edit to |
| 711 | + `pyproject.toml` can switch it off where it cost something. Enabling the rule also found a |
| 712 | + genuinely dead parameter in `ExperimentRunner._charge`, which took `artifacts` and billed |
| 713 | + from `result.outputs`; it is deleted rather than exempted. |
| 714 | +3. **Running both arms and comparing what they bought.** Only an execution can prove a switch is |
| 715 | + connected — an argument can be read, passed on, and still reach nothing, which is exactly what |
| 716 | + happened here. Marked slow, two items, and it fails on the pre-fix kernel. |
| 717 | + |
| 718 | +**What v6 is evidence of.** Not conservative sizing, which it never ran. B9 minus B8 is an |
| 719 | +*unplanned negative control*: two arms that were operationally identical, differing only in |
| 720 | +registration id and therefore in the Custodian's evaluation draw. Its interval is a measured |
| 721 | +noise floor for this ladder at three replicates — coverage +0.033 [−0.011, +0.089], and verdict |
| 722 | +accuracy −0.044 [−0.117, +0.028] computed afterwards and labelled as such. **The interval |
| 723 | +covered zero on a contrast whose true difference is zero**, which is the first calibration check |
| 724 | +this project's bootstrap has had. One draw cannot establish coverage, and it is not offered as |
| 725 | +if it could; it is one honest observation where there were none. |
| 726 | + |
| 727 | +It also sets the scale for the neighbours. v6's other three contrasts — B4−B3 = +0.039, |
| 728 | +B6−B4 = +0.006, B6−B7 = −0.044 — are all the size of a difference now known to be zero, and all |
| 729 | +three already span zero. The bootstrap was saying so; the control is the first thing to |
| 730 | +corroborate it from outside. |
| 731 | + |
| 732 | +**v6 stands on the record.** Its lock file is committed, its verdict is reported as refuted, and |
| 733 | +the paper says in the same breath that the refutation adjudicates nothing. Deleting a registered |
| 734 | +protocol's result because the run embarrassed the code is the exact move this project exists to |
| 735 | +make impossible. **Protocol v7 re-registers v6's prediction** — same ladder, same adjudicated |
| 736 | +contrast, hash `0dac6ca41fdb` — against a switch that is connected. It has not been run. |
| 737 | + |
| 738 | +**The shape, for the third time.** M20: a switch that acts only through a model, reported as |
| 739 | +mechanism. M22: a switch that reaches no code at all. M23: a switch that reaches the kernel and |
| 740 | +is dropped inside it. Each was found by asking a *different* question about the same claim — |
| 741 | +that an arm's declared mechanisms are the mechanisms it runs — and each time the previous |
| 742 | +milestone's check was structurally unable to see the new case. |
| 743 | + |
| 744 | +**And a fourth hand-maintained list.** The CI job that re-scores every committed results file |
| 745 | +named five paths in a tuple. There were six on disk, so `results.v6.lock.json` — the run this |
| 746 | +whole milestone is about — would have gone through CI unchecked. It is a glob now, and it fails |
| 747 | +loudly if the glob matches nothing rather than passing vacuously. |
| 748 | + |
| 749 | +**And M18's bug, one shape along.** Every chapter heading in `FINDINGS.md` after the first has |
| 750 | +been rendering glued to the previous paragraph — `…rests on them.### Protocol v2` — since the |
| 751 | +document existed. A `{%- else %}` swallowed the newline that ended the footnote. M18's structural |
| 752 | +test asserts that a heading has a blank line before it, and could not see this because the line |
| 753 | +does not start with a hash; the drift check could not see it because the file and the generator |
| 754 | +were wrong together. It is fixed, and the guard is now *no hash anywhere but the start of a |
| 755 | +line*, which fails on five lines of the previously committed file. |
| 756 | + |
| 757 | +**Acceptance** |
| 758 | +- `conservative` is passed at both `_escalate` call sites, and B9 buys strictly more seeds than |
| 759 | + B8 on items that escalate below the ceiling. |
| 760 | +- `ruff check` selects `ARG` and passes; the kernel is not exempted, and an AST test asserts the |
| 761 | + same rule over it independently of the config. |
| 762 | +- The AST guard fails on `git show HEAD~1:src/nullius/kernel.py` with exactly one finding, |
| 763 | + `_replicate(conservative)`. |
| 764 | +- Protocol v7 verifies, rebuilds identically, and appears in the paper as registered and not yet |
| 765 | + run. |
| 766 | +- The paper and `FINDINGS.md` carry the flaw and the limitation, and CI's drift check passes. |
| 767 | +- CI re-scores every `benchmark/results*.lock.json` found by glob — six of them, not the five |
| 768 | + the tuple named — and exits non-zero if it finds none. |
| 769 | +- No `#` appears anywhere but the start of a line in `FINDINGS.md`, which the file committed at |
| 770 | + `8460649` fails on five lines. |
| 771 | + |
| 772 | +--- |
| 773 | + |
674 | 774 | ## 2. Working agreement |
675 | 775 |
|
676 | 776 | - **`main` is always green.** Every milestone lands as a PR with CI passing on Linux and Windows. |
|
0 commit comments