clay-borg/decisions/ADR-0018-a-number-that-does-not-move.md

127 lines
5.7 KiB
Markdown
Raw Normal View History

# ADR-0018: a number that does not move when its subject moves is measuring something else
status: accepted
date: 2026-08-07
decided by: agent, under the standing loop authorization
tier: M (structural M — adds an admissibility requirement, changing how
the loop constrains its own operation; ADR-0006 D5). chaos d8 = 7 → no
override. **Declaration 1 of chaos window 3.**
references: [CB-WP-0030](../workplans/CB-WP-0030-a-number-that-does-not-move.md),
CB-EV-0019 §1 (where the family was named),
[GameDesign.md](../specs/GameDesign.md) §1,
[RetrospectiveAnalysis.md](../specs/RetrospectiveAnalysis.md) §4.1,
[ADR-0017](ADR-0017-chaos-window-2-verdict.md) (instance 7)
## Context
Seven claims in this project have been wrong in the same way: the
arithmetic was right and the **subject** was not. Four reached a document
or another repository.
## D1 — what this does not do, stated first
**`facts-check`'s untagged surface is not this family's control.** It
reports **62** untagged literal copies across 7 registry keys and fails on
none. That is a real gap, and closing it would have caught **at most one**
of the seven:
- Instance 1's denominator **was** a registry fact and **was correct**
`am4a_target` = 3,798 is right; AM-4a simply does not cover the code
being priced.
- Instances 2, 4, 5 and 6 were **fresh measurements** with no registry key
at all.
> **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 is **named as
separate work**. Bundling it here would let this decision claim a victory
it did not win — which is the family's own error, applied to itself.
## D2 — the rule
> **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.**
This is not new machinery. It is the generalisation of two things this
project already built, each in response to one instance:
- `RetrospectiveAnalysis` §4.1's **policy panel** — born from instance 5,
which is why a single policy's win rate may not be reported as a
difficulty;
- `attack-value.rs`'s **one-varying-number sweep** — built for F17, which
varies ATTACK's rank and holds everything else fixed.
**Both were built after an instance. The rule requires them before.**
## D3 — the yield, instance by instance, including the misses
| # | the claim | would D2 have caught it? |
|---|---|---|
| 1 | `csv` against AM-4a's headroom | **yes** — vary which crates are in scope and the budget moves; that *is* the error |
| 2 | *"12 in the file"* | **no** — but §1.2's row-level rule, born from it, does |
| 3 | SOLVE's wrong condition | **no** — not a number |
| 4 | 112161 µs per node | **yes** — cost must rise with branching; it fell, and the tell was already printed |
| 5 | too easy at 56 seats | **yes** — vary the policy and the rate collapses to 0% |
| 6 | 2 Focus/Blame conflicts | **partly** — varying seat count would scale a real conflict; it is the weakest row |
| 7 | window 2 had zero overrides | **no** — a count that was never made has no sensitivity to state |
**Three clear, one partial, three missed.**
**The two it catches outright are the two that travelled furthest**, and
both were bound for `ground-game`: instance 4 sat in a survey, instance 5
was queued for a repo blocked on that exact number. A control that catches
the consequential half is worth having even at 3/7.
**What it misses is stated so nobody stops looking.** Instance 7 needs
counting, not sensitivity. Instance 3 was not a number. **No syntactic gate
detects a wrong subject in general** — it is a semantic error, and
claiming otherwise would build a gate that cannot cash out (ADR-0006 D3,
ADR-0017 D2).
## D4 — where it binds
- **Register findings whose claim is arithmetic** — the same scope
GameDesign §1.2 already uses.
- **Anything leaving the repo.** Four of the seven were outbound or nearly
so, and the recipient caught one we did not.
**Not** every number in every artifact. A rule that binds everywhere is
obeyed nowhere, and this project has 62 untagged literals as evidence of
what a broad, unenforced requirement produces.
## D5 — half of it is mechanical, and only half
`loop-lint` can check that a sensitivity line **is present**. It cannot
check that the sensitivity is the **right one**, because that is the same
judgement the rule exists to force.
> **The lint checks presence, not adequacy, and must say so in its own
> output** — otherwise a green lint reads as *"the claim was verified"*,
> and the control becomes a way of not looking.
That is the honest split: the mechanical half stops the requirement being
forgotten; the judgement half stays with the author and the reviewer, who
between them caught five of the seven.
## Consequences
- `GameDesign.md` §1 gains a fourth clause. The existing three are
untouched — each was bought by an earlier instance.
- `loop-lint` gains a check over the register's arithmetic rows.
- Existing rows are brought under it or exempted **with a reason**.
- The untagged-facts surface is recorded as separate, unclosed work.
## What was rejected
| rejected | why |
|---|---|
| closing the untagged-facts surface as *the* control | 1 of 7; a tag reproduces a wrong subject faithfully |
| requiring a regenerating command on every claim | catches transcription, and six of seven were not transcription errors |
| a rule binding every number everywhere | obeyed nowhere; the 62 untagged literals are the evidence |
| a lint that claims to verify the claim | it can only see presence, and pretending otherwise trains the eye to stop at green |
| stating a yield of "the family is closed" | the family's own error, applied to itself |