(type=CUSTOMER, customer_id=X) — customer's own overlay
Mutable on currentVersionId, matcherHash, and deletedAt only. Every
change to the matcher, status, priority, value, or label/description
inserts a new product_activation_rule_version row and re-points
currentVersionId (and re-stamps matcherHash from the new version) in
the same transaction.
Duplicate-matcher detection — no two non-deleted rules in the same
(route_id, customer_id, type) scope sharing the same matcher hash — is
enforced by product_activation_rule_unique_tuple, a partial unique
index keyed on (route_id, customer_id, type, matcher_hash) NULLS NOT DISTINCT WHERE deleted_at IS NULL. matcherHash is denormalized onto
this row from the current version so the constraint can live here;
services must catch unique-violation errors on insert/update and surface
them as DUPLICATE_MATCHER.
Per-route activation rules — identity row. Three valid tier shapes (CHECK-enforced):
Mutable on
currentVersionId,matcherHash, anddeletedAtonly. Every change to the matcher, status, priority, value, or label/description inserts a newproduct_activation_rule_versionrow and re-pointscurrentVersionId(and re-stampsmatcherHashfrom the new version) in the same transaction.Duplicate-matcher detection — no two non-deleted rules in the same
(route_id, customer_id, type)scope sharing the same matcher hash — is enforced byproduct_activation_rule_unique_tuple, a partial unique index keyed on(route_id, customer_id, type, matcher_hash) NULLS NOT DISTINCT WHERE deleted_at IS NULL.matcherHashis denormalized onto this row from the current version so the constraint can live here; services must catch unique-violation errors on insert/update and surface them asDUPLICATE_MATCHER.