Reading Isn't Proof
When two or more implementations share one contract, a code read that concludes
"they agree" is a hypothesis, not a result. Do not report "there's a test gap,
but no defect" and stop. Write the executable comparison, run it, and let it
decide.
If a gap is worth naming out loud, it is worth the ~30 minutes the battery costs.
One implementation is out of scope, and that is not a hedge. A promise with a
single implementation is a definition, not a claim that can diverge — this rule
does not license writing batteries for everything. Two is the threshold, and a
fake plus the real thing it stands in for is the best case of two, because that
pair is what everyone else's tests are silently trusting. Where a shared battery
for the contract already exists, extend it rather than fork it.
The rule
Close a named test gap in a shared contract even when you believe there is no
defect behind it. The battery is cheap; the reading is not proof.
The reason is not that code reads are usually wrong. It's the shape of the
error they make: you generalize from the part you inspected to the whole. You
read the method where the interesting promise lives, satisfy yourself that all
implementations agree there, and conclude "this contract is consistent" — never
putting the neighbouring method's behavior side by side, because you have already
decided. A battery carries no such prior. It checks the axis you skipped.
Procedure
- Enumerate the promises. Read the contract's own docstrings/spec text and
list every testable guarantee. Grep for promise language across the interface:
idempotent, no-op, never, always, guaranteed, exactly once,
at most one, must never, fails closed. Written promises with no test are
the highest-yield place to look.
- One shared module, one check per promise, parametrised over every
implementation. Not per-backend test files — those are how the gap formed.
- Assert the discriminating detail, not the outcome (see below).
- Include a positive control so the battery cannot pass vacuously.
- Run it before deciding whether there was a defect. Then report what it
actually said.
For the concrete file shape this converged on, see
references/battery-template.md.
Three properties, or the battery proves nothing
A battery that runs green against every implementation may be measuring nothing
at all. Each of these has been the difference, and all three are in
references/battery-craft.md with the case that
produced them:
- Assert the discriminating detail. Checking that both implementations raise is not conformance; checking that both raise the same kind, with the same message exposure and the same retryability, is.
- Exercise the discriminating state. A contract that differs only on the empty case, the concurrent case, or the second call is a contract whose battery must reach those states deliberately.
- Keep a positive control. One assertion that fails when the shared code is broken. Without it, "all backends agree" and "the battery never ran" are the same green.
Reporting
State three things: what the battery covers, what it found, and what you changed.
- Found a divergence → name the axis, both behaviors, and the user-visible
consequence (status code, wrong value, duplicate execution).
- Found nothing → "battery green across N implementations; the gap was coverage,
not correctness." That is a finished job, not an empty one.
Quick checklist
Related skills
fewer-tests-more-proof — the suite-wide economics: battery-ifying a
multi-implementation contract is one of its consolidation moves; this skill
owns the battery craft.
self-audit — its verification-honesty pass is where this rule fires during a
branch audit.
readable-code — check names are the battery's documentation; each one
states its claim. Absent it, name each check for the promise it tests, not
for the function it calls.
rfc-writer — when the battery surfaces a contract question too big to settle
in the fix.
1---2name: reading-isnt-proof3description: Use when one contract has two or more implementations — adapters, backends, clients, or a fake standing in for the real thing — and you are about to report that nothing tests something. Not for a single implementation, and not where a shared battery already exists.4---56# Reading Isn't Proof78When two or more implementations share one contract, **a code read that concludes9"they agree" is a hypothesis, not a result.** Do not report "there's a test gap,10but no defect" and stop. Write the executable comparison, run it, and let it11decide.1213If a gap is worth naming out loud, it is worth the ~30 minutes the battery costs.1415**One implementation is out of scope, and that is not a hedge.** A promise with a16single implementation is a definition, not a claim that can diverge — this rule17does not license writing batteries for everything. Two is the threshold, and a18fake plus the real thing it stands in for is the best case of two, because that19pair is what everyone else's tests are silently trusting. Where a shared battery20for the contract already exists, extend it rather than fork it.2122## The rule2324> Close a named test gap in a shared contract even when you believe there is no25> defect behind it. The battery is cheap; the reading is not proof.2627The reason is not that code reads are usually wrong. It's the *shape* of the28error they make: **you generalize from the part you inspected to the whole.** You29read the method where the interesting promise lives, satisfy yourself that all30implementations agree there, and conclude "this contract is consistent" — never31putting the neighbouring method's behavior side by side, because you have already32decided. A battery carries no such prior. It checks the axis you skipped.3334## Procedure35361. **Enumerate the promises.** Read the contract's own docstrings/spec text and37 list every testable guarantee. Grep for promise language across the interface:38 `idempotent`, `no-op`, `never`, `always`, `guaranteed`, `exactly once`,39 `at most one`, `must never`, `fails closed`. Written promises with no test are40 the highest-yield place to look.412. **One shared module, one check per promise**, parametrised over *every*42 implementation. Not per-backend test files — those are how the gap formed.433. **Assert the discriminating detail, not the outcome** (see below).444. **Include a positive control** so the battery cannot pass vacuously.455. **Run it before deciding whether there was a defect.** Then report what it46 actually said.4748For the concrete file shape this converged on, see49[references/battery-template.md](references/battery-template.md).5051## Three properties, or the battery proves nothing5253A battery that runs green against every implementation may be measuring nothing54at all. Each of these has been the difference, and all three are in55[references/battery-craft.md](references/battery-craft.md) with the case that56produced them:5758- **Assert the discriminating detail.** Checking that both implementations raise is not conformance; checking that both raise the *same kind*, with the same message exposure and the same retryability, is.59- **Exercise the discriminating state.** A contract that differs only on the empty case, the concurrent case, or the second call is a contract whose battery must reach those states deliberately.60- **Keep a positive control.** One assertion that fails when the shared code is broken. Without it, "all backends agree" and "the battery never ran" are the same green.6162## Reporting6364State three things: what the battery covers, what it found, and what you changed.6566- Found a divergence → name the axis, both behaviors, and the user-visible67 consequence (status code, wrong value, duplicate execution).68- Found nothing → "battery green across N implementations; the gap was coverage,69 not correctness." That is a finished job, not an empty one.7071## Quick checklist7273- [ ] Does this contract have ≥2 implementations?74- [ ] Am I about to report a gap without running anything?75- [ ] Is there one shared module, parametrised over all implementations?76- [ ] Does every check assert a discriminating detail (kind, state, value)?77- [ ] Does each check run in the state the production caller actually produces?78- [ ] Is there a positive control that makes the key check observable?79- [ ] Can each check fail for a reason I can name out loud?80- [ ] Did I run it *before* concluding whether a defect exists?8182## Related skills8384- `fewer-tests-more-proof` — the suite-wide economics: battery-ifying a85 multi-implementation contract is one of its consolidation moves; this skill86 owns the battery craft.87- `self-audit` — its verification-honesty pass is where this rule fires during a88 branch audit.89- `readable-code` — check names are the battery's documentation; each one90 states its claim. Absent it, name each check for the promise it tests, not91 for the function it calls.92- `rfc-writer` — when the battery surfaces a contract question too big to settle93 in the fix.