An inference feature contract defines types, units, absence rules, event clocks and rejection behavior before a record reaches a model.
Feature contracts: admit only usable inference records
Define the boundary that the model actually needs
A receipt-risk model expects amount in minor currency units, a merchant age in days and a transaction timestamp. Calling all three “numbers” is not a contract. Specify accepted types, units, allowable ranges, missing-value policy and the version of each field definition. A value of 4700 means something different in cents and whole currency units. Training-serving parity compares values after this boundary; the admission gate first decides whether values are valid enough to compare.
Separate malformed, absent and stale values
A missing merchant age can be an allowed unknown, while an age of minus 47 days signals a clock or join defect. A timestamp from an event that arrives 82 minutes late is different again. Give each state its own decision: reject, substitute an explicit missing indicator, or route to a fallback. Do not quietly cast strings to floats when a source changes its schema. The returned decision should name the violated rule without copying sensitive payloads into logs. Inference logging covers that diagnostic boundary.
Tie the contract to a model version
A new model might accept a category that the old model cannot encode. Keep the expected contract with the model artifact and validate a candidate against both the incoming stream and the rollback model before promotion. A schema file alone is weak evidence; run the same validation on a frozen slice containing nulls, extremes and source revisions. Promotion gates should require the contract result for the exact artifact digest.
Measure rejection as a production signal
Track admitted, rejected and fallback counts by feature-contract version and low-cardinality source class. A sudden jump in rejected receipts may be a provider defect rather than a model failure. Review representative rejected records under controlled access and inspect the first bad source revision. Test malformed types, changed units, nonfinite values and future timestamps before a release. The admission project turns these cases into a release gate.
Implementation
from datetime import datetime, timezone
from math import isfinite
def admit_receipt(receipt, now):
amount = receipt.get("amount_minor")
if type(amount) is not int or not 0 <= amount <= 4_700_000:
return {"state": "reject", "reason": "amount-contract"}
age = receipt.get("merchant_age_days")
if age is not None and (type(age) not in (int, float) or
not isfinite(age) or not 0 <= age <= 36_500):
return {"state": "reject", "reason": "merchant-age-contract"}
occurred = receipt.get("occurred_at")
if not isinstance(occurred, datetime) or occurred.tzinfo is None:
return {"state": "reject", "reason": "event-clock-contract"}
if occurred > now:
return {"state": "reject", "reason": "future-event"}
return {"state": "admit", "features":
{"amount_minor": amount, "merchant_age_days": age}}
clock = datetime(2026, 10, 6, tzinfo=timezone.utc)
valid = {"amount_minor": 4_700, "merchant_age_days": None,
"occurred_at": clock}
assert admit_receipt(valid, clock)["state"] == "admit"
assert admit_receipt({**valid, "amount_minor": True}, clock)["state"] == "reject"
Performance and operating cost
Validation is O(f) time for f checked fields and O(f) space for the accepted feature copy. Per-record validation cost is small beside model inference, but rejected-record review and quarantine storage grow with bad-source volume. Do not let an unlimited quarantine queue turn one schema incident into a storage incident.
Common Mistakes
- Treating cents and whole currency units as interchangeable.
- Silently converting malformed values instead of recording a contract failure.
- Using one null rule for every feature.
- Logging the full rejected receipt in a broad-access metrics stream.
Read next
- Feature schema evolution: keep producers and rollback models compatible
- Project: release a versioned receipt feature admission gate
- Training-serving parity: compare feature values at one prediction clock
- Model promotion: require evidence before changing the serving pointer
- Feature Stores Tutorial
Continue the workflow: Training source admission: provenance, trust tiers and quarantine.
