clay-borg/workplans/CB-WP-0030-a-number-that-does-not-move.md
tegwick d7279483f6
Some checks failed
ci / check (push) Failing after 4s
CB-WP-0030: a control for the wrong-subject family
Seven claims in this project have been wrong the same way — the arithmetic
right, the subject wrong. Four reached a document or another repo.

ADR-0018 states the rule: a measured claim names the variable it depends
on and shows the measurement moving with it. GameDesign §1.4 adds it as a
fourth admissibility clause; the existing three are untouched.

Honest yield is 3 clear + 1 partial of 7. It misses a count never made and
a claim that is not a number, and D3 names those so nobody stops looking.
The tempting fix — closing facts-check's 62 untagged literals — would have
caught 1 of 7 and is deferred as separate work rather than bundled.

loop-lint gains check_sensitivity_stated over arithmetic register rows,
mutation-proven red on F17 and green when restored, with four self-test
controls. It checks presence, never adequacy, and says so in its output.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 11:35:41 +02:00

224 lines
9.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
id: CB-WP-0030
kind: meta
title: "A number that does not move: a control for the wrong-subject family"
status: done
state_hub_workstream_id: "e6a0dad9-9b45-41fb-92ab-07c8b5690366"
---
# Purpose
```
structural tier M (changes how the loop constrains its own operation —
it adds an admissibility requirement, ADR-0006 D5)
chaos d8 = 7 → no override
declared tier M
```
**Declaration 1 of chaos window 3**, opened 2026-08-07 at d8 by ADR-0017.
## The family, with every instance
Seven claims in this project have been wrong in the same way. CB-EV-0019
§1 named it: *this project's arithmetic errors are not in the arithmetic.*
| # | pass | the claim | what was wrong | caught by |
|---|---|---|---|---|
| 1 | CB-WP-0021 | `csv` costs 21,613 against 3,798 of headroom | AM-4a **never sees** that code | re-deriving in ADR-0011 |
| 2 | → ground-game | *"12 in the file"* | a sum with no deal table | **the recipient** |
| 3 | CB-WP-0018 | SOLVE offered on a face-down Problem | the wrong condition named | implementing the ruling |
| 4 | CB-WP-0025 | 112161 µs per node | the timer bracketed whole games | **adversarial review** |
| 5 | CB-WP-0025 | the game is too easy at 56 seats | a win rate attributed to the game | **adversarial review** |
| 6 | CB-WP-0029 | 2 Focus/Blame conflicts | compared *other* players' discs | re-reading the check |
| 7 | ADR-0017 | window 2 had zero overrides | never counted; cited 4× | writing the verdict |
## What will not work, measured before proposing anything
**`facts-check` has a real gap and it is not this one.** It reports **62
untagged literal copies** across 7 registry keys and fails on none of
them — a drift surface it names and does not close.
**Closing it would have caught at most one of the seven.** Instance 1's
denominator *was* a registry fact and was *correct*; the error was that
AM-4a does not cover the code. Instances 2, 4, 5, 6 were **fresh
measurements** — no registry key existed, and a provenance tag reproduces
a wrong subject faithfully.
> **A tag proves a number came from somewhere. It cannot prove the
> somewhere was the right place.**
The untagged surface is worth closing on its own merits, and **bundling it
here would let this pass claim a victory it did not win.** Named as
separate work, not done here.
## What did work, and it is one thing
Read the *caught by* column. Every instance was caught when **the number
was computed a second time by something with a different purpose** — a
later pass, a reviewer, the recipient, or the author re-reading.
Two of them, 4 and 5, share a sharper property: **the claim was tested
against a variation.**
- #4's cost **fell** as branching **rose**. A per-node cost cannot do that.
The tell was in the published output.
- #5's win rate was attributed to the game; a second policy on the same
deals gave 0% where the first gave 100%.
And #1 has it too: vary which crates are in scope and the budget changes,
which is the whole error.
**So the control is to generalise the thing this project already builds
after each instance** — RetrospectiveAnalysis §4.1's policy panel,
CB-WP-0029's attack-value sweep — and require it *before*:
> **A measured claim must name the variable it depends on, and show the
> measurement moving with it. A number that does not move when its subject
> moves is measuring something else.**
**Honest yield: 3 or 4 of 7**, and it catches the two that travelled
furthest. It does **not** catch #7 (a count never made) or #3 (not a
number). **Claiming it closes the family would be the family's own error
applied to itself.**
## Task: decide the rule and its limits
```task
id: CB-WP-0030-T01
status: done
priority: high
state_hub_task_id: "4be3630e-4c67-4ef9-a2f6-f689208b26b1"
```
`decisions/ADR-0018-*.md`.
- **State the rule** and where it binds: register findings that carry
arithmetic, and anything leaving the repo.
- **State the yield honestly**, instance by instance, including what it
misses. A control whose reach is overstated is worse than none, because
it stops people looking.
- **Decide whether it is a gate or a discipline.** A sensitivity claim is
prose; `loop-lint` can check *presence*, never *adequacy*. Say which
half is mechanical.
- **Do not bundle the untagged-facts surface.** Name it as separate.
**Done 2026-08-07.**
[ADR-0018](../decisions/ADR-0018-a-number-that-does-not-move.md).
**D1 states what it does not do, first**, because the tempting fix was the
wrong one: closing the 62 untagged literals would have caught **1 of 7**,
and instance 1's denominator was a registry fact that was *correct*.
Deferred as separate work rather than bundled.
**D3 gives the yield instance by instance: three clear, one partial, three
missed.** The two it catches outright are the two that travelled furthest,
both bound for `ground-game`. **The misses are named** so nobody stops
looking — a count never made has no sensitivity, and instance 3 was not a
number.
**D5 splits it honestly**: the lint sees *presence*, never *adequacy*, and
its output says so — otherwise a green run reads as *"the claim was
verified"* and the control becomes a way of not looking.
## Task: the rule, in the spec that governs findings
```task
id: CB-WP-0030-T02
status: done
priority: high
state_hub_task_id: "cefcc917-91ef-4dbb-ab92-743266874b81"
```
`specs/GameDesign.md` §1 already requires a reproduction that **exists**,
has the **ruled shape**, and **can fail**. This adds the fourth clause,
because §1.2's row-level rule was itself born from instance 2 and covers
only sums.
**Controls:**
- the clause names the variable and the expected direction, or it is a
sentence with no content;
- **the existing three clauses are unchanged** — this is an addition, and
a rewrite would silently drop what earlier instances bought.
**Done 2026-08-07.** `GameDesign.md` §1.4, with the two instances that
motivate it stated in it: a per-node cost that **fell** as branching
**rose**, and a win rate attributed to the game that collapsed to 0% under
a second policy. **Both were already printed when they shipped.**
## Task: make the mechanical half mechanical
```task
id: CB-WP-0030-T03
status: done
priority: high
state_hub_task_id: "b773bc85-1f7b-4563-a0b5-3d5007162846"
```
`loop-lint` fails when a register row of an arithmetic kind lacks a
sensitivity line.
**Controls:**
- the check must **fail on a real row**, demonstrated by mutation — a lint
that cannot go red is decoration (ADR-0006 D3, ADR-0017 D2);
- **it checks presence, not adequacy**, and its output says so, so nobody
reads a green lint as *"the claim was verified"*;
- existing register rows are brought under it or explicitly exempted with
a reason — a rule that only binds future rows leaves the six instances
unprotected.
**Done 2026-08-07.** `check_sensitivity_stated` in `loop-lint`.
**It fired on three real rows immediately — F12, F15, F21 — and all three
were `note`s.** A note is by definition a finding *without* a
reproduction (§3.1), so there is no measurement to be sensitive about;
requiring one would be asking for a sensitivity statement about nothing.
**That exclusion belongs in the check, not in three hand-written
exemptions**, and putting it there is the difference between a rule and a
pile of special cases.
**Mutation-proven on a real row.** Strip every sensitivity word from F17's
prose and the check goes red; restore it and green. The first mutation
attempt *failed to fire* because F17's text still said "one number varied"
— which is the check working, and worth recording as the moment it was
nearly mis-verified.
Four self-test controls: it catches a bare claim, **clears one that names
its variable** (without which it would fire on everything), skips
non-arithmetic kinds, and skips notes.
## Task: evidence
```task
id: CB-WP-0030-T04
status: done
priority: medium
state_hub_task_id: "7206661a-a046-47bb-8f73-4b56328151af"
```
`evidence/CB-EV-0028-*.md`.
- **Re-run the seven instances against the rule** and report how many it
would have caught. If the answer is below 3, the rule is not the one.
- **What the untagged surface is worth**, since it was measured here and
deferred.
- **Whether a lint that checks presence is worth having at all**, or
whether it trains the eye to stop at green.
**Done 2026-08-07.**
[CB-EV-0028](../evidence/CB-EV-0028-a-number-that-does-not-move.md).
**Yield 3 clear + 1 partial of 7** — above the falsifier, and the two it
catches outright are the two that travelled furthest. Row 6 is recorded as
*partly* rather than as a catch, because the variation would have
*prompted* the question, not *tested* it.
**The untagged surface is worth 1 of 7 against this family** and is still
worth closing for drift — recorded as separate, unclosed work.
**The presence-only lint is worth having on one condition**: it must never
grow a claim it cannot cash. The day its output implies verification,
ADR-0006 D3 applies and it should be deleted.
**§6 records a vocabulary defect in this very workplan** — it shipped
`kind: process` against `product|meta|mixed` and `make self-tests` caught
it. The pass that added a gate was caught by an older one.