> ## Documentation Index
> Fetch the complete documentation index at: https://docs.kaireonai.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Decisioning Gate inheritance + time-aware windows

> W7.1 — most-specific-scope dedupe and asOf time-window evaluation on /api/v1/offers/:id/effective-rules. Default off; gated by tenantSettings.aiAnalyzerSettings.qualification.inheritanceEnabled.

## 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:

```
offer > subcategory > category > global
```

The dedupe is implemented by `resolveEffectiveRules` in
`lib/qualification/inheritance.ts`.

### Configuration

```json theme={null}
{
  "aiAnalyzerSettings": {
    "qualification": {
      "inheritanceEnabled": true
    }
  }
}
```

Default off — every existing tenant is unchanged. With the flag off,
`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 a `config.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

```json theme={null}
{
  "timeWindow": {
    "daysOfWeek": [0, 1, 2, 3, 4],
    "startHour": "09:00",
    "endHour": "17:00",
    "from": "2026-04-01",
    "to": "2026-04-30",
    "timezone": "Europe/London",
    "blackoutDates": ["2026-04-25"]
  }
}
```

All fields optional; unset fields don't constrain. Conventions:

* `daysOfWeek` uses **0 = Monday … 6 = Sunday**, so `[0, 1, 2, 3, 4]` is Mon–Fri.
* `startHour` / `endHour` are `"HH:MM"` 24-hour. Overnight windows are supported — if `startHour > endHour` (e.g. `"22:00"`–`"06:00"`) the window wraps past midnight.
* `from` / `to` are inclusive `"YYYY-MM-DD"` date bounds.
* `timezone` is an IANA name (default `UTC`).
* `blackoutDates` are `"YYYY-MM-DD"` dates that are always excluded.

### Honest fail-open on malformed windows

When a rule has `config.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:

```json theme={null}
{
  "cascadeTrace": {
    "min_age": [
      { "scope": "global", "scopeId": null, "ruleId": "qr-global" },
      { "scope": "offer",  "scopeId": "o1", "ruleId": "qr-offer" }
    ]
  }
}
```

Useful for compliance UIs that need to render "rule X applies because
of category Y" with a full chain.

## Behavioral guarantees

The resolver (`getEffectiveRules` in `lib/effective-rules.ts`) upholds these invariants:

* **Default options preserve every matching rule.** With `inheritanceMode` defaulting to `"all"` and no `asOf`, 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 rule `name` (precedence `offer > subcategory > category > global`).
* **`explainCascade: true` populates `cascadeTrace`** with the per-name list of every contributing candidate, and implies `deduped`.
* **`asOf` filters by `timeWindow`:** rules whose window excludes the instant are dropped; rules with no `timeWindow`, or whose window includes the instant, are kept.
* **Malformed `timeWindow` falls open** — the rule is preserved rather than dropped.
