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

# Fairness hard-gate

> Block a decision flow from publishing when disparate-impact thresholds are breached. Continuous re-eval auto-pauses on drift. Four-eyes governance override for documented exceptions.

## What this solves

Regulated-finance, public-sector, and healthcare pilots need to prove that the decisioning system does not discriminate against protected groups. The platform reports four fairness metrics today (`disparate impact`, `equal opportunity`, `demographic parity`, `counterfactual`), but reporting alone doesn't stop a flow from going live when those metrics breach. The hard-gate is the enforcement layer.

## How it works

Two enforcement points:

1. **Pre-publish gate** — `POST /api/v1/decision-flows/publish` evaluates the policy against the last 7 days of decision traces. If any threshold is breached, the call returns **422 Unprocessable Entity** with the violation list, and the flow stays in its previous status. The publish is blocked, not deferred.
2. **Continuous re-check cron** — `GET /api/v1/cron/fairness-recheck` runs daily; for every tenant that has opted into `continuousRecheck`, it re-evaluates the gate and **flips active flows back to `paused`** when thresholds drift. Audit-logged with the violation reasons.

Override is via the existing four-eyes governance — set `fairnessPolicy.override.{approvedBy, expiresAt}` and the gate skips with an audit-logged warning until the override expires.

## Step 1 — Configure the policy

```bash theme={null}
curl -X PUT https://playground.kaireonai.com/api/v1/tenant-settings \
  -H "Content-Type: application/json" -H "X-Requested-With: XMLHttpRequest" \
  -d '{
    "fairnessPolicy": {
      "enabled": true,
      "sensitiveAttribute": "ageBand",
      "thresholds": {
        "disparateImpactRatio": 0.8,
        "demographicParityGap": 0.2,
        "equalOpportunityGap": 0.2
      },
      "minSampleSize": 100,
      "continuousRecheck": true
    }
  }'
```

* `disparateImpactRatio: 0.8` is the EEOC four-fifths rule (29 CFR § 1607.4D) — the lowest selection rate across protected groups must be at least 80% of the highest.
* `demographicParityGap: 0.2` blocks publishes where any pair of groups differs in selection rate by more than 20 percentage points.
* `equalOpportunityGap` requires labeled outcomes (the customer would have converted); skipped automatically when labels aren't available.
* `minSampleSize: 100` — the gate skips with a logged "insufficient samples" reason when fewer than 100 usable decision traces exist in the 7-day window.
* `continuousRecheck: true` enables the post-publish drift cron.

## Step 2 — Make sure the trace carries the sensitive attribute

The hard-gate reads `decision_traces.requestAttributes[sensitiveAttribute]` (added in manual-sql migration 22). Any recommend that passes the attribute in `body.attributes` is automatically eligible:

```bash theme={null}
curl -X POST https://playground.kaireonai.com/api/v1/recommend \
  -H "Content-Type: application/json" -H "X-Requested-With: XMLHttpRequest" \
  -d '{
    "customerId": "cust-12345",
    "decisionFlowKey": "your-flow",
    "attributes": { "ageBand": "65+", "tier": "platinum" }
  }'
```

Without the attribute on the request, that trace becomes unusable for fairness sampling — the gate will skip with a "no usable samples" message until enough traces with the attribute exist.

## Step 3 — Try to publish

```bash theme={null}
curl -X POST https://playground.kaireonai.com/api/v1/decision-flows/publish \
  -H "Content-Type: application/json" -H "X-Requested-With: XMLHttpRequest" \
  -d '{ "id": "<flow-id>" }'
```

When the gate fires:

```json theme={null}
{
  "title": "Fairness gate blocked publish",
  "detail": "One or more fairness thresholds configured on this tenant were breached over the last 7 days of decision traces.",
  "violations": [
    "disparate impact ratio 0.612 < threshold 0.8",
    "demographic parity gap 0.314 > threshold 0.2"
  ],
  "metrics": {
    "sampleSize": 1247,
    "disparateImpactRatio": 0.612,
    "demographicParityGap": 0.314,
    "equalOpportunityGap": 0.187
  },
  "override": "To bypass, an admin can set tenant.settings.fairnessPolicy.override = { approvedBy, expiresAt } via four-eyes governance."
}
```

The flow remains in its previous published state. Nothing rolls out.

## Step 4 — Four-eyes override (when justified)

Documented exceptions are bypassed via the override block — both fields required:

```bash theme={null}
curl -X PUT https://playground.kaireonai.com/api/v1/tenant-settings \
  -H "Content-Type: application/json" -H "X-Requested-With: XMLHttpRequest" \
  -d '{
    "fairnessPolicy": {
      "enabled": true,
      "sensitiveAttribute": "ageBand",
      "thresholds": { "disparateImpactRatio": 0.8, "demographicParityGap": 0.2, "equalOpportunityGap": 0.2 },
      "minSampleSize": 100,
      "continuousRecheck": true,
      "override": {
        "approvedBy": "compliance-team@company.com",
        "expiresAt": "2026-07-01T00:00:00Z"
      }
    }
  }'
```

While the override is active, every gate evaluation logs `"Fairness gate bypassed via active override"` with the `approvedBy` value — audit trail for the compliance team.

## What gets auto-paused

When the continuous re-check cron detects drift on a tenant with `continuousRecheck: true`:

* Every flow with `status: "active"` is updated to `status: "paused"`.
* A `decision_flow` audit-log row is written with `action: "auto_pause"`, `reason: "fairness_recheck_drift"`, and the violation list.
* Operators see paused flows in Studio with the audit reason visible in the change history.

To re-enable, fix the upstream cause (model retrain, feature change, segment adjustment), let the next decision-trace window accumulate, and re-publish.

## Gotchas

* **Sample sourcing requires manual-sql/22.** Run `psql ... -f prisma/manual-sql/22_decision_trace_request_attributes.sql` once; this adds the `requestAttributes` JSONB column and a GIN index. Without it, the gate skips with `"no usable samples"`.
* **The 7-day window is fixed.** If your traffic is low, set `minSampleSize` accordingly or increase trace sampling rate (`decisionTraceSampleRate`).
* **`continuousRecheck` only auto-pauses currently-active flows.** Re-publish brings them back; the gate evaluates again at that point.

## Proof reference

T35 of the proof bundle (this section is forthcoming) will capture the full breach → block → override cycle with verbatim payloads.
