Concepts¶
Four objects and one contract.
The context¶
StructuredGateContext bundles everything the checks might need — the model,
the validation data, and whatever optional inputs you have. It is a plain
dataclass; nothing is computed at construction.
context = StructuredGateContext(
model=my_model,
X=X_val,
y_true=y_val,
y_pred=y_pred,
protected_df=protected_val,
latencies_ms=benchmark_latencies,
cost_per_inference=0.0008,
model_card=my_model_card,
expected_loss=technical_premium,
generate_fn=my_llm_explainer,
task="binary",
)
Checks¶
Each check answers one question and returns a list of CheckResult. A result
carries a flag:
| Flag | Meaning |
|---|---|
OK |
passed |
NOT_APPLICABLE |
skipped — a needed input or dependency is missing, or the check does not apply to this task |
CHECK_ERROR |
the check raised; always treated as blocking |
| a risk string | PROXY_RISK, PII_LEAKAGE_RISK, LOSS_RATIO_RISK, … |
result.is_ok treats OK and NOT_APPLICABLE as fine, so a skipped check
never blocks a deploy.
Graceful degradation¶
The gate grades what you give it. This is the contract the whole library is built around:
- No
protected_df→ fairness checks reportNOT_APPLICABLE - No
model_card→ the compliance check reportsNOT_APPLICABLE - No
expected_loss→ loss-ratio parity reportsNOT_APPLICABLE task="regression"→ classification-only checks reportNOT_APPLICABLE- shap not installed → the SHAP check reports
NOT_APPLICABLE
Skipped checks stay in the report rather than disappearing, each with the reason. A reviewer can see what was not evaluated, which matters more than it sounds: a governance report that silently omits a check is worse than one that says it was skipped.
The verdict¶
GateReport.gate_status applies one rule:
- Any blocking check flagged → BLOCKED
- Otherwise any flag at all → NEEDS_REVIEW
- Otherwise → PASS
Performance, compliance and security are blocking. Fairness is not.
That is a design stance, not an oversight. A proxy-correlation finding might be a genuine problem or a legitimate rating factor, and only someone who knows the business can say which. A gate that hard-fails on it gets switched off; a gate that routes it to a reviewer gets used.
Tasks¶
context.task is "auto", "binary", "multiclass" or "regression".
"auto" infers from y_true and logs what it inferred.
Inference is genuinely ambiguous — a claims-frequency target of 0/1/2/3 is
indistinguishable from a four-class problem by shape alone — so the guess is
never silent. Set task explicitly for anything you gate on.
Each check declares supported_tasks, so a check that cannot answer
meaningfully for your task says so instead of producing a confident number.
Reports¶
report.gate_status # "PASS" | "NEEDS_REVIEW" | "BLOCKED"
report.task # what it was graded as
report.model_metric # which metric produced the score
report.model_score
report.flags # non-OK, non-NOT_APPLICABLE results
report.by_category("fairness")
report.to_json("gate_report.json")
model_metric sits next to model_score deliberately. A score is
uninterpretable without knowing what produced it, so the report always names
it — including when a metric fell back to a substitute.