A grep for one small expression — pick the business's trading name, fall back to its legal name — returned forty matches across routes, components and library code. None of them was wrong. Every copy did the right thing.
One surface had no copy at all: the invoice list, which had therefore been showing operators registered legal entities for months on a screen where nobody recognises them.
That is the failure mode worth naming. The problem was never duplication — forty copies of a three-token expression cost almost nothing to maintain. The problem was that no list existed of who was following the rule, so nothing could notice the surface that never received it. A rule spread by copying propagates to wherever someone remembered; a rule with a home propagates to wherever someone imports.
Give the rule a home, and register the home
The fix is unremarkable: one module, pure, no I/O, that answers the question once. The part worth copying is the second step — the home goes into a register in the same commit that creates it. An architecture decision record carries a table of cross-cutting rules and the module that owns each; a new home that is not in that table is a home the next author will not find, and they will hand-roll copy forty-one.
A home should also refuse to own a rule that already has one. This resolver filters placeholder identity text — half-finished names an operator typed while chasing real details — but it does not carry the pattern for detecting them. That belongs to an existing module and is imported. A second definition of "what counts as placeholder text" would be a new drift surface disguised as a convenience.
Notice the rule that points the other way
Here is the part a one-directional lock misses.
The display rule says: on an operator's screen, lead with the trading name. There is a counterpart rule pointing the opposite way: on a tax invoice, a statement, an outbound financial email and the accounting mirror, use the registered legal entity. That is a compliance requirement, not a preference.
So the surfaces are partitioned into two sets with contradictory obligations. A structural test that only asserts "the operator surfaces import the resolver" proves adoption and says nothing at all about the second set. And the second set is where the expensive defect lives: a trading name on a tax-invoice recipient line is a compliance problem, while a legal name on an operator's screen is merely annoying.
So the lock runs both ways:
- The operator surfaces must import the resolver, and must not contain a hand-rolled fallback.
- The statutory surfaces must not import the resolver, and must still be reading the legal name.
Both assertions are cheap. Only having both makes the boundary real.
Give the lock teeth, and check them
The first version of the "no hand-rolled fallback" assertion was a regex over the source of the invoice surfaces. It failed the build immediately — on the very line that consumed the new home correctly, because a null-coalescing normalisation looked enough like the forbidden expression to match.
The fix was to tighten the pattern so it required the choice between the two names rather than any use of either. The interesting part is what had to be added alongside it: a teeth check. A regex that has been narrowed to remove a false positive can be narrowed again until it matches nothing at all, and a lock matching nothing passes silently forever. So the test now asserts, before it scans a single file, that its own pattern still matches three known-bad samples and does not match two known-good ones.
A structural lock is code. It deserves the same suspicion as the code it guards, and the cheapest expression of that suspicion is a fixture the lock must still catch.
What the locked tests caught that review would not
The assertions were written first and confirmed failing before any implementation existed. Two defects surfaced that a careful reading would have missed:
- A negative zero. Days-until-due for an invoice due today was computed by negating a zero, producing
-0— a real value in JavaScript that survives onto the wire in JSON. The assertion compared against0and failed on identity. - An N+1. The assertion was not a timing check or a result check but a call-count spy on the client table: exactly one read for a page of twenty-five invoices across five clients. A per-row lookup with memoisation still fails that, which a result-shape assertion would have passed.
Both were fixed in the code. Two other assertions turned out to be wrong themselves and were changed — one had silently date-stamped a record's creation in UTC rather than the tenant's timezone, making a draft's age off by a day. Changing a locked assertion is allowed exactly when the assertion, not the code, is what got the rule wrong; the discipline is that the change and the reason for it are reported rather than absorbed.
The principle
Extract a rule when it is a rule, not when it is repeated. Register the home so the next author can find it. And when the rule has a counterpart that points the other way, write the lock in both directions — because the boundary you did not assert is the one that moves.