A lint rule instead of a reviewer
Rupees are integer paise in Rekhon, and the rule is enforced by a custom Android Lint module that fails the build. The interesting part is why a reviewer was not enough.
Compiled from the repository’s decision records and changelog rather than written as a first-person account. The reasoning is the project’s; the summarising is editorial. The record itself is linked at the end.
Binary floating point cannot represent 0.10. A rupee amount held in a Double is therefore wrong the moment it is added, split or accumulated — and wrong by an amount too small to notice, until a reconciliation disagrees months later. That delay is the whole problem: the bug does not announce itself when it is cheap to fix.
The project had already written the rule down. CLAUDE.md §3 called a floating-point amount review-blocking, which makes it a thing a reviewer must spot. ADR-0001 records the step from there to build-blocking: a custom Android Lint module, so the rule stops depending on attention.
The heuristic, and why there has to be one
Kotlin cannot express “this Double is money” in its type system. That is the reason the Money type exists at all, and it is also why the detector cannot simply ban a type. It requires two halves to be true at once: the declared type is floating point, and the name looks monetary. Both, because either alone is wrong — banning Double would block a perfectly good animation fraction, and matching only names would miss nothing and catch nothing.
val amountMinor: Double // ./gradlew lint FAILS — monetary name, floating-point type
val amountMinor: Long // passes — the representation the codebase wants
val scrollFraction: Double // passes — floating point, but not moneyA name heuristic is a judgement call, and the ADR treats it as one rather than pretending otherwise. The trade-off it sets is explicit:
Prefer missing an exotic case over blocking valid code.
So the word list stays narrow — terms that are monetary in this domain and little else — and it is maintained in the ADR rather than edited in the detector, so a change to what counts as money arrives with its reasoning attached.
The module did not stay a single rule. It now carries six detectors, each one a project rule that used to live in prose: the money type, the domain clock, GlobalScope, hand-rolled crypto, hardcoded UI strings, and PII in logs.