Inheritance dedupe
Without the flag,getEffectiveRules returns every rule whose
scope matches the offer (global, category, subcategory, offer). When
two rules share a name (e.g., a global “min_age=18” and an
offer-specific “min_age=21”), both flow through.
When inheritanceEnabled is on (or ?explainCascade=true is
supplied), the resolver collapses duplicates to the most-specific
scope per name using the precedence:
resolveEffectiveRules in
lib/qualification/inheritance.ts.
Configuration
getEffectiveRules runs in inheritanceMode: "all", which returns the
full matching rule set exactly as before (no dedupe, no time filter).
Time-aware windows
Rules can declare aconfig.timeWindow shape. The resolver checks
each rule’s window against the request’s asOf timestamp when the
route is called with ?asOf=ISO8601. Rules whose window does not
include asOf are filtered out.
Window shape
daysOfWeekuses 0 = Monday … 6 = Sunday, so[0, 1, 2, 3, 4]is Mon–Fri.startHour/endHourare"HH:MM"24-hour. Overnight windows are supported — ifstartHour > endHour(e.g."22:00"–"06:00") the window wraps past midnight.from/toare inclusive"YYYY-MM-DD"date bounds.timezoneis an IANA name (defaultUTC).blackoutDatesare"YYYY-MM-DD"dates that are always excluded.
Honest fail-open on malformed windows
When a rule hasconfig.timeWindow set but the value is not a plain
object, the filter falls open (the rule is preserved). This matches
the existing “rules must work even when storage shape drifts” contract
elsewhere in the codebase. Schema validation upstream is the place to
enforce strict shape.
explainCascade
?explainCascade=true implies inheritance dedupe and populates
cascadeTrace in the response — a per-name list of every candidate
that contributed to the dedupe:
Behavioral guarantees
The resolver (getEffectiveRules in lib/effective-rules.ts) upholds these invariants:
- Default options preserve every matching rule. With
inheritanceModedefaulting to"all"and noasOf, the output is unchanged from the pre-inheritance behavior — every rule whose scope matches the offer is returned. inheritanceMode: "deduped"collapses duplicates to the single most-specific scope per rulename(precedenceoffer > subcategory > category > global).explainCascade: truepopulatescascadeTracewith the per-name list of every contributing candidate, and impliesdeduped.asOffilters bytimeWindow: rules whose window excludes the instant are dropped; rules with notimeWindow, or whose window includes the instant, are kept.- Malformed
timeWindowfalls open — the rule is preserved rather than dropped.