Jev recipe / data residency & compliance
Jev Data Residency: Gate Every Decision by Region
Make the cross-border transfer decision a typed decision. One Jev evaluate call returns the lawful processing route, the transfer risk, and the override flag — before any personal state crosses a border.
Gate decisions by region in six steps
{
"processing_route": {
"type": "choice",
"instructions": "Under which lawful route may this state be processed?",
"criteria": {
"eu_in_region": "Evaluation can stay inside the subject\u2019s zone (EEA, Japan, Brazil local)",
"third_country_adequacy": "An adequacy decision covers the destination country for these data classes",
"sccs_with_tia": "Standard contractual clauses apply and the TIA reference is on file",
"needs_dpa_review": "No covered route \u2014 legal decides before any transfer"
}
},
"transfer_risk": {
"type": "score",
"instructions": "If this state crossed a border unlawfully, how costly? 1 (aggregate, non-identifying) to 4 (special categories or regulator-sensitive)"
},
"regional_override": {
"type": "noul",
"instructions": "Does this request demand stricter handling than its region\u2019s default route?"
}
}Six requests, one zone gate
Every request below already ran through the zone gate. Move the two axes — the auto-pin route-confidence threshold and the maximum auto-transfer risk — and watch the lanes recompute. A 0.96 route confidence is not enough on its own: the EEA health-data request still leaves the automated path, because transfer risk is the second axis.
| Request | Route & confidence | Transfer risk | Lane |
|---|---|---|---|
Clickstream routing · EEA user #2214 EEA · behavioral · aggregate · 94ms | in-region0.97 | risk 1/4 | auto_pinned |
Symptom triage · EEA user #4457 EEA · special category · health · 101ms | in-region0.96 | risk 4/4 | minimize_then_pin |
Statement classification · Japan user #7741 Japan · financial records · 88ms | SCCs + TIA0.91 | risk 2/4 | auto_pinned |
Content personalization · US user #2298 United States · behavioral · identifiers · 96ms | adequacy0.74 | risk 1/4 | minimize_then_pin |
Credit screening · Brazil user #5190 Brazil · financial records · 92ms | needs review0.68 | risk 3/4 | legal_review |
Badge access decision · EEA employee #3306 EEA · special category · biometric · 99ms | in-region0.95 | risk 3/4override | legal_review |
Demo data only — six simulated requests. The lanes recompute from the same two-axis rule documented in the recipe copy.
Residency enforcement: the typed zone gate vs prompt-level rules vs default API routing
criteria encode your transfer map as versioned, diffable config
advisory text a busy runner can truncate past
the record shows the route, its confidence, and the map version that decided
transfer_risk shares the gate, so special-category data stays out of auto-pinning even at 0.97
exceptions are queryable, not folklore
every affected historical transfer falls out of a query
the criteria are the single source of transfer truth
Side-by-side code: drifting app-code checks vs the typed zone gate
def pick_endpoint(user: dict) -> str:
# The transfer map as scattered if-statements. Nothing records
# which route applied, there is no risk axis, no review lane: a
# health-data request from an EEA user rides the same default
# endpoint as an aggregate US metric. When the adequacy list
# changes, every service patches its own copy - or forgets.
if user.get("region") in ("DE", "FR", "NL"):
return "https://eu.api.typesafe.ai/v1/jev/evaluate"
return "https://api.typesafe.ai/v1/jev/evaluate"import requests
GATE_ENDPOINT = "https://api.typesafe.ai/v1/jev/evaluate"
AUTO_ROUTE_CONFIDENCE = 0.85
AUTO_MAX_RISK = 2 # risk <= 2 AND confidence >= gate -> auto-pin
ZONE_ENDPOINTS = {
"eu_in_region": "https://eu.api.typesafe.ai/v1/jev/evaluate",
"third_country_adequacy": "https://api.typesafe.ai/v1/jev/evaluate",
"sccs_with_tia": "https://api.typesafe.ai/v1/jev/evaluate",
}
QUESTIONS = {
"processing_route": {
"type": "choice",
"instructions": "Under which lawful route may this state be processed?",
"criteria": {
"eu_in_region": "Evaluation can stay inside the subject's zone",
"third_country_adequacy": "An adequacy decision covers the destination",
"sccs_with_tia": "SCCs apply and the TIA reference is on file",
"needs_dpa_review": "No covered route - legal decides first",
},
},
"transfer_risk": {
"type": "score",
"instructions": "Costly if this crossed a border unlawfully? 1 aggregate - 4 special categories",
},
"regional_override": {
"type": "noul",
"instructions": "Does this request demand stricter handling than its region default?",
},
}
def gate_transfer(state: dict) -> dict:
resp = requests.post(
GATE_ENDPOINT,
json={"state": state, "questions": QUESTIONS},
timeout=5,
)
resp.raise_for_status()
data = resp.json()
route = data["processing_route"]
risk = data["transfer_risk"]["answer"]
if route["answer"] == "needs_dpa_review" or data["regional_override"]["answer"]:
lane = "legal_review" # no automated transfer, ever
elif route["confidence"] >= AUTO_ROUTE_CONFIDENCE and risk <= AUTO_MAX_RISK:
lane = "auto_pinned"
else:
lane = "minimize_then_pin" # tokenize/strip, evaluate reduced state
return {
"lane": lane,
"endpoint": ZONE_ENDPOINTS.get(route["answer"]),
"residency_record": {
"subject_region": state.get("region"),
"data_classes": state.get("data_classes"),
"route": route["answer"],
"route_confidence": route["confidence"],
"transfer_risk": risk,
"zone_map_version": "zone-map@2026.10",
"tia_ref": state.get("tia_ref")
if route["answer"] == "sccs_with_tia"
else None,
},
}// The transfer map as one environment variable. Every service
// reads the same value and none of them can say why. A Japanese
// user's financial records and an aggregate US metric take the
// same default path, and the audit question "under which lawful
// route did this transfer happen" has no answer in the system.
const EVALUATION_ENDPOINT =
process.env.REGION === "eu"
? "https://eu.api.typesafe.ai/v1/jev/evaluate"
: "https://api.typesafe.ai/v1/jev/evaluate";
export const pickEndpoint = () => EVALUATION_ENDPOINT;const GATE_ENDPOINT = "https://api.typesafe.ai/v1/jev/evaluate";
const AUTO_ROUTE_CONFIDENCE = 0.85;
const AUTO_MAX_RISK = 2;
type Lane = "auto_pinned" | "minimize_then_pin" | "legal_review";
const QUESTIONS = {
processing_route: {
type: "choice",
instructions: "Under which lawful route may this state be processed?",
criteria: {
eu_in_region: "Evaluation can stay inside the subject's zone",
third_country_adequacy: "An adequacy decision covers the destination",
sccs_with_tia: "SCCs apply and the TIA reference is on file",
needs_dpa_review: "No covered route - legal decides first",
},
},
transfer_risk: {
type: "score",
instructions:
"Costly if this crossed a border unlawfully? 1 aggregate - 4 special categories",
},
regional_override: {
type: "noul",
instructions:
"Does this request demand stricter handling than its region default?",
},
} as const;
export async function gateTransfer(state: {
region: string;
data_classes: string[];
tia_ref?: string;
}) {
const resp = await fetch(GATE_ENDPOINT, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ state, questions: QUESTIONS }),
});
if (!resp.ok) throw new Error("Jev evaluate failed");
const data = await resp.json();
const route = data.processing_route as {
answer: string;
confidence: number;
};
const risk = data.transfer_risk.answer as number;
const lane: Lane =
route.answer === "needs_dpa_review" || data.regional_override.answer
? "legal_review"
: route.confidence >= AUTO_ROUTE_CONFIDENCE && risk <= AUTO_MAX_RISK
? "auto_pinned"
: "minimize_then_pin";
// Residency record = the sibling of the audit-trail decision
// record. Replay the next zone-map version against logged states
// to find every transfer the new map would block.
return {
lane,
residency_record: {
subject_region: state.region,
data_classes: state.data_classes,
route: route.answer,
route_confidence: route.confidence,
transfer_risk: risk,
zone_map_version: "zone-map@2026.10",
tia_ref: route.answer === "sccs_with_tia" ? state.tia_ref : null,
},
};
}Jev data residency FAQ
What is data residency, and why does it matter for AI decisions?
Data residency is the requirement that data be processed or stored inside a specific jurisdiction. It bites AI decisions in one particular place: the state you send to an evaluation endpoint often carries personal data, so every evaluate call is potentially a cross-border transfer. If your routing service evaluates an EEA user’s health questionnaire through an endpoint in another region without a lawful transfer route, the classification — not just the storage — is the violation. This recipe makes the where-may-this-be-processed call an explicit, gated, logged decision instead of a side effect of your default endpoint.
Does GDPR require EU data to stay in the EU?
No — GDPR restricts transfers outside the EEA but does not forbid them. Chapter V (Articles 44–49) allows transfers under an adequacy decision (Art. 45), appropriate safeguards such as standard contractual clauses (Art. 46), or limited exceptions (Art. 49). "Data residency in the EU" is a simplification some teams choose because it collapses that analysis to one route; this recipe supports the general case by making the route itself the typed decision — in-region, adequacy, SCCs, or legal review — rather than hardcoding the simplification.
Does Jev offer EU (or Japan) endpoints?
That is a provider fact to verify, not something this recipe can decide for you — the same honesty rule we apply to compatibility claims: endpoint regions, sub-processor locations, and DPA commitments change, so check the provider’s current documentation before pinning production traffic. What the recipe guarantees is on your side of the boundary: whichever endpoints exist, the gate that decides which state may go where is explicit, versioned, and logged — and if no in-zone endpoint exists for a lane, the lane degrades to minimization (tokenize, evaluate the reduced state) instead of silently transferring.
What is a transfer impact assessment (TIA)?
Under SCCs (post-Schrems II), a TIA documents whether the destination country’s law undermines the contractual protections — government access, onward transfer, redress — and which supplementary measures (encryption, splitting, transparency commitments) close the gap. It is a legal document your team maintains; this recipe does not produce it. What the recipe does is operational: the sccs_with_tia route only proceeds when a TIA reference is on file, and that reference rides in the residency record, so "which TIA covered this transfer" is a query, not an archaeology project.
How does this work with the decision audit trail?
The two records are siblings. The audit-trail record answers "what was decided, how confident, under which schema versions"; the residency record answers "where was this state allowed to be processed, under which lawful route and map version". Both are fixed-size rows derived largely from the same evaluate response, and both replay: when the zone map changes — an adequacy decision is annulled, a provider moves hosting — re-run logged states under the new map and diff the lanes, exactly like a schema upgrade. An exception is then provable end to end: here is the decision, and here is the route that lawfully carried it.
How often do the routes change — and what do I do about it?
Enough to matter: adequacy decisions get revised and occasionally annulled, model SCC clauses get updated, providers open and retire regional endpoints, and your product enters new markets with new rules (Japan’s APPI cross-border rules and Brazil’s LGPD have their own transfer logic). The recipe’s answer is the versioned zone map: every record names the map version it decided under, a quarterly diff of map versions against the residency log surfaces affected transfers, and changing the map is a config release — not a rewrite of scattered region checks across services.