Skip to main content

Fairness hard-gate (publish-time enforcement)

POST /api/v1/decision-flows/publish runs the fairness hard-gate as a pre-publish check when the tenant has opted in. If configured thresholds breach, the publish is blocked with HTTP 422 and a structured violation report — the new flow version is not written.

Configuration

Set on tenant.settings.fairnessPolicy:

Behavior

  • Not configured / disabled — gate is a no-op, publish proceeds.
  • Active override (not expired) — gate is skipped, publish proceeds, audit log records the bypass.
  • Insufficient samples — gate is skipped (enforced: false, reason explains samples < minSampleSize).
  • Thresholds breached — publish blocked with 422:
  • Infrastructure error — fail-open (publish proceeds, warning logged) so a transient DB blip doesn’t block legitimate compliance work.

Sample-source caveats

The gate derives the protected group from decision_trace.requestAttributes[sensitiveAttribute] (the canonical source), falling back to qualificationResults[*].context.attributes[sensitiveAttribute] for traces persisted before the request-attributes snapshot migration. A trace counts as a positive decision when it selected at least one offer. If fewer than minSampleSize of the last 7 days of traces carry the sensitive attribute, the gate skips with an “insufficient samples” reason. Backed by the platform’s fairness hard-gate enforcement helper.

Tiered fairness evaluation

POST /api/v1/fairness/evaluate?metrics=basic|advanced runs the full fairness pipeline. The query string controls which metrics tier is returned.

Basic tier (default)

Existing demographic-parity, four-fifths-rule, equal-opportunity, and equalized-odds gap calculations from lib/fairness/metrics.ts. Unchanged behavior — every existing caller is bit-identical.

Advanced tier

Adds intersectional analysis + mitigation recommendations from lib/fairness/advanced.ts:
  • Intersectional cells require per-sample intersectionalGroups: { axisName: groupValue }. The route runs the intersectional evaluator with a default minimum cell size of 10 samples and surfaces the cells plus the worst disparate-impact ratio.
  • Mitigation recommendations are derived from the report shape (DI ratio, four-fifths violation, equal-opportunity gap) — no extra inputs needed.
  • Gini-by-group (giniByGroup) — the Gini coefficient of each group’s binary decision distribution (computed for groups with more than one sample).
  • KS-by-group (ksByGroup) — a two-sample Kolmogorov–Smirnov test of each non-reference group’s decision distribution against the first group’s. Both are returned automatically in the advanced tier — no modelKey required.
When the caller asks for advanced but doesn’t supply intersectionalGroups, the response includes advancedAwaitingConfig: ["intersectional: no per-sample intersectionalGroups supplied"] so operators know why the analysis is empty. No silent fallback.

Auto-run counterfactual fairness + LIME (needs a modelKey)

When metrics=advanced and the body carries a modelKey, the route resolves a real scorer via resolveScorerForFairness (V1 supports gradient_boosted only) and auto-runs two more primitives on top of the basic tier:
  • lime — runs computeLime against the scorer when a limeBaseline attribute set is supplied. Fairness-route LIME defaults to 200 samples (override via limeOptions.samples).
  • counterfactualFairness — runs evaluateCounterfactualFairness when the request is mode: "inline" and supplies modelGroupKey + counterfactualGroup, with per-sample attributes to score.
Both blocks are best-effort: if the model can’t be resolved, or the required inputs are missing, the reason surfaces in advancedAwaitingConfig and the basic tier still returns. Individual- fairness (Lipschitz) and the DeLong AUC comparison also live in lib/fairness/advanced.ts but are not auto-run from this route — callers invoke them directly.

EU AI Act report

POST /api/v1/fairness/report runs the same fairness pipeline and returns a formatted report: Body shape mirrors /evaluate. Optional title + subtitle override the defaults (“Fairness Assessment Report” / “EU AI Act Article 10 § 2(f)”).

Audit trail

Every call to /evaluate and /report writes one audit-log row (action: fairness_evaluate or fairness_report) so DSAR exports can cite the exact report contents.