Build the constraint, not the check

Make the wrong thing hard to produce

Geschrieben von Timo Rieber am 14. März 2026

The gate - a binary pass/rewrite verdict on the argument before any surface review runs - caught structural problems before polish could bury them. A different pattern survived: stock transitions, uniformly-sized paragraphs, code walkthroughs without an argument. Not structural failures. The gate let them through. But I kept seeing the same findings, flagged by review, every draft.

When the same finding keeps appearing

I turned each recurring pattern into a generation rule:

  • “No ‘The good news:’ as a transition.”
  • “Sections should differ in length.”
  • “No closing paragraphs that mechanically restate what was already said.”

The list kept growing. Every time review flagged a pattern twice, I moved it upstream into the generation step. The generation step got narrower. Review shifted from catching known defects to spotting things only a second perspective would notice - a factual claim that doesn’t match git history, a dead link, a section that undermines the opening’s premise.

A review finding that recurs isn’t a review problem. It’s a generation problem. Turn it into a generation constraint, and the category disappears from review.

The same shift in different material

cloudapps handles money across payment, invoicing, and refund calculations. Without a value object, every function that touches an amount needs to round, validate precision, check currency match. Those checks multiply with every consumer.

@dataclass(frozen=True)
class Money:
    amount_cents: int = 0
    currency_code: str = 'EUR'

    @classmethod
    def of(cls, amount: Decimal | float | str, currency_code: str = 'EUR') -> Self:
        rounded_amount = Decimal(amount).quantize(
            Decimal('0.01'), rounding='ROUND_HALF_UP'
        )
        return cls(int(rounded_amount * 100), currency_code)
Python

Money.of('149.99') rounds once, converts to integer cents, freezes the result. Operators enforce currency match. Downstream code never rounds, never validates, never checks. The constructor handles it once.

I built the same type independently in mainzelmen. Different product, no shared code, same constraint.

A recurring inspection is a missing constraint. Every check I moved upstream - from review into generation, from application code into a frozen constructor - didn’t just catch the problem earlier. It eliminated the category.