Build journal

A lint rule instead of a reviewer

Rekhon

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.

MoneyLintKotlin

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.

what the detector does and does not flagkotlin
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 money

A 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.
ADR-0001

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.