CB-WP-0030: a control for the wrong-subject family
Some checks failed
ci / check (push) Failing after 4s

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>
This commit is contained in:
tegwick 2026-08-07 11:35:41 +02:00
parent ffd2279e16
commit d7279483f6
6 changed files with 503 additions and 6 deletions

View file

@ -1,8 +1,9 @@
---
id: CB-WP-0030
kind: process
kind: meta
title: "A number that does not move: a control for the wrong-subject family"
status: ready
status: done
state_hub_workstream_id: "e6a0dad9-9b45-41fb-92ab-07c8b5690366"
---
# Purpose
@ -84,8 +85,9 @@ applied to itself.**
```task
id: CB-WP-0030-T01
status: todo
status: done
priority: high
state_hub_task_id: "4be3630e-4c67-4ef9-a2f6-f689208b26b1"
```
`decisions/ADR-0018-*.md`.
@ -100,12 +102,31 @@ priority: high
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: todo
status: done
priority: high
state_hub_task_id: "cefcc917-91ef-4dbb-ab92-743266874b81"
```
`specs/GameDesign.md` §1 already requires a reproduction that **exists**,
@ -119,12 +140,18 @@ only sums.
- **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: todo
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
@ -139,12 +166,33 @@ sensitivity line.
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: todo
status: done
priority: medium
state_hub_task_id: "7206661a-a046-47bb-8f73-4b56328151af"
```
`evidence/CB-EV-0028-*.md`.
@ -155,3 +203,22 @@ priority: medium
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.