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.

AD

Gate decisions by region in six steps

01Put region and data class into the state, not into guesses. Residency enforcement is only as good as its inputs: the subject’s region (from account or billing — not from IP alone), the data classes present in the state (behavioral aggregates, financial records, special categories), and nothing inferred at eval time. A zone decision made on the wrong region silently invalidates every downstream control, so the state schema carries region and data_classes as required fields, and the criteria say what to do when sources conflict.
02Make the processing route a typed Choice, not a config lookup. The question is not "where does the API run" but "under which lawful route may this state be processed": eu_in_region (evaluation stays inside the subject’s zone), third_country_adequacy (an adequacy decision covers the destination), sccs_with_tia (standard contractual clauses plus a transfer impact assessment on file), or needs_dpa_review (no covered route — legal decides before any transfer). Encoding the route as criteria turns your transfer map into a versioned contract you can diff, replay, and show an auditor — the same move the decision audit trail makes for the decision itself.
03Gate on transfer risk, not just confidence. A 0.97 on aggregate clickstream routing and a 0.97 on special-category health data are not the same transfer. The transfer_risk Score (1 = aggregate, non-identifying state; 4 = special categories or data a regulator would call sensitive) shares the gate: auto-pin only when the route is high-confidence AND risk is low; route the middle band to deterministic minimization; and let risk 4 block the automated path even at high confidence — the two-axis discipline the audit trail applies to reversibility, applied to geography.
04Route by lanes that match your deployment topology. eu_in_region pins the call to the in-zone evaluation endpoint; adequacy routes to the named destination; sccs_with_tia proceeds only with the TIA reference in the log; needs_dpa_review blocks the automated path entirely. If no in-zone endpoint exists for a lane, degrade to processing minimization — tokenize or strip identifying fields and evaluate the reduced state. Residency enforcement must fail toward less data crossing borders, never silently toward more.
05Log the residency record next to the decision record. The audit trail logs what was decided; the residency record logs where it was allowed to be processed: subject region, data classes, chosen route with confidence, transfer risk, the zone-map version in force, and the TIA reference when SCCs applied. Six fields, one fixed-size row. When an adequacy decision is annulled or a provider changes hosting, replay the new zone map against logged states to find every historical transfer the new map would block — replay as migration check, the same discipline as schema upgrades.
06Re-check the map on a schedule, not after an incident. Adequacy decisions are revised and sometimes annulled, SCC templates get updated, providers add and retire regions, and products enter new markets. Keep the zone map as versioned config with an effective date, name the version in the criteria block so every record shows which map it decided under, and diff map versions against the residency log quarterly — the monitor-the-distribution discipline the audit trail applies to confidence, applied to jurisdiction.
schema / processing-route, transfer-risk & override contract
{
  "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?"
  }
}
Send a real request state and inspect processing_route, transfer_risk, and regional_override with calibrated confidence.

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.

auto_pinned · 2minimize_then_pin · 2legal_review · 2
RequestRoute & confidenceTransfer riskLane

Clickstream routing · EEA user #2214

EEA · behavioral · aggregate · 94ms

in-region0.97risk 1/4auto_pinned

Symptom triage · EEA user #4457

EEA · special category · health · 101ms

in-region0.96risk 4/4minimize_then_pin

Statement classification · Japan user #7741

Japan · financial records · 88ms

SCCs + TIA0.91risk 2/4auto_pinned

Content personalization · US user #2298

United States · behavioral · identifiers · 96ms

adequacy0.74risk 1/4minimize_then_pin

Credit screening · Brazil user #5190

Brazil · financial records · 92ms

needs review0.68risk 3/4legal_review

Badge access decision · EEA employee #3306

EEA · special category · biometric · 99ms

in-region0.95risk 3/4overridelegal_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

METRIC
Jev
Default API routing / hardcoded region checks
What decides the zone
A typed Choice over lawful routes

criteria encode your transfer map as versioned, diffable config

A prompt paragraph ("process EU users in the EU")

advisory text a busy runner can truncate past

Explaining a blocked transfer
The criteria block IS the explanation

the record shows the route, its confidence, and the map version that decided

Post-hoc reasoning about what the prompt probably meant; default routing says nothing at all
Risk and confidence interplay
Two axes

transfer_risk shares the gate, so special-category data stays out of auto-pinning even at 0.97

One axis at best (a confidence-like score on the text); default routing has neither
Auditing an exception
SCC lane carries the TIA reference in the same fixed-size record

exceptions are queryable, not folklore

Approvals scattered across tickets and chat threads, unlinked to the transfers they covered
When regulations change
Replay the new zone map against logged states

every affected historical transfer falls out of a query

Manual re-review of whatever anyone remembers; default routing discovers changes by incident
Multi-region products
One versioned contract all services read

the criteria are the single source of transfer truth

N services, N drifted copies of the if-statement; default routing is one silent path for all of them

Side-by-side code: drifting app-code checks vs the typed zone gate

python / baseline: the region check that lives in app code and drifts
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"
python / jev: one evaluate call returns route, risk, and override
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,
        },
    }
typescript / baseline: the env-var switch nobody can explain later
// 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;
typescript / jev: the zone gate, typed end to end
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.

Extend the compliance and privacy stack