Skip to content

Duplicated business rule

The corpus prior

A rule is short, so inline it.

The training-data shape is that a small expression has no owner and gets re-typed wherever it is needed. That is right for genuinely small rules and wrong for rules the framework already owns, because the inline copy has no way to track the canonical one.

Wrong shape

Re-deriving a rule that has a helper:

# WRONG — minor units are not always /100
major = Decimal(int(minor)) / 100
path = f"/{surface.name.replace('_', '-')}"     # missing .lower()
denied = set(deny_roles)                        # no prefix normalisation

Right shape

Import the owner:

# RIGHT
from dazzle.core.ir.money import get_currency_scale
from dazzle.core.strings import entity_slug, normalize_role

major = Decimal(int(minor)) / (10 ** get_currency_scale(code))
path = f"/{entity_slug(surface.name)}"
denied = {normalize_role(r) for r in deny_roles}

Why this matters here

Three real instances shipped behind green gates:

Rule Canonical owner Second copy Consequence
Minor-unit → major core.ir.money.get_currency_scale format_cell._currency divided by a literal 100, formatted ,.2f JPY 1500 rendered 15.00 JPY — 100x wrong — in tables and CSV, while the edit form showed the truth. Zero-decimal currencies understated 100x; 3-decimal dinars overstated 10x.
Role-name normalisation core.strings.normalize_role check_deny_roles normalised neither side A role_-prefixed DB role never intersected a bare DSL persona name, so deny: silently stopped denying. Latent only because nothing emitted the prefix yet.
Entity → URL slug core.strings.entity_slug three sites did .replace("_","-") without .lower() For a mixed-case name the registered route and the breadcrumb link to it disagreed — the #1421/#1426 dead-link class.

All three passed every gate that existed at the time.

Why the gates missed them

Structural gates key on a code shape against a baseline that already contains the debt:

  • test_clone_ratchet clusters by token structure; these copies differ in arity, so they never shared a signature.
  • test_dedup_footgun_gates greps for the literal text of formulas it was authored with. Decimal(x) / 100 was not one of them.
  • test_api_surface_drift pins what exists, not who uses it.

The missing check is: "N sites implement this rule — do they agree?" That needs a value-table comparison against the canonical function, not a pattern match. test_money_scale_parity.py is the current answer for currency.

When the pattern must be local

Some .replace("_", "-") calls are a different rule, not this one:

  • CLI flags — --param-name in mcp/cli_help.py
  • locale codes — en_GB → en-GB in i18n/display_locale.py
  • free-text search candidates — cli/spec.py
  • word-splitting to build a label — render/open_discovery.py

A gate that flags those gets disabled in a week, so test_slug_case_consistency.py pins a file list rather than the whole tree. Adding a genuinely new slug site means adding it to that list.