"""Decision rules from Dify (14 Oct 2026). Owner request, 14 Oct 2026: "Dify'de hazirlansa oradan gelse". The model a Codex step talks to stays an OmniRoute combo, because a Dify app would not pass Codex's own tools through. What Dify supplies is the rules: the decision questions, thresholds and which combo each kind of request goes to. The bridge fetches them in the background (policy.refreshMinutes), validates them against the local settings and stores the last good copy in jev-policy.json. A turn never waits for Dify; a failed fetch keeps the last good rules. The Dify workflow returns one output named ``kurallar``: a JSON object (or JSON text) whose keys are any of jev_settings.POLICY_KEYS, each holding the same fields as the local settings section, for example {"autoRoute": {"enabled": true, "targets": {"zor": "Ajan"}}}. """ import copy import json import time import urllib.error import urllib.request import jev_settings UA = "AITURK-IDE-codex-bridge/karar-politikasi" class _YonlendirmeYok(urllib.request.HTTPRedirectHandler): """A redirect would carry the Authorization header to another host; it is an error instead.""" def redirect_request(self, req, fp, code, msg, headers, newurl): raise urllib.error.HTTPError(req.full_url, code, f"redirect refused: {newurl}", headers, fp) _ACICI = urllib.request.build_opener(_YonlendirmeYok) MAX_BYTES = 64 * 1024 _son = {"zaman": 0.0} def ayikla(cikti, yerel): """Dify output -> validated overrides. Raises ValueError with a short reason when unusable.""" if isinstance(cikti, str): if len(cikti.encode("utf-8")) > MAX_BYTES: raise ValueError("kurallar too large") cikti = json.loads(cikti) if not isinstance(cikti, dict): raise ValueError("kurallar is not an object") bilinmeyen = sorted(set(cikti) - set(jev_settings.POLICY_KEYS)) if bilinmeyen: raise ValueError("unknown sections: " + ", ".join(bilinmeyen)) overrides = {k: v for k, v in cikti.items() if isinstance(v, dict)} if len(overrides) != len(cikti): raise ValueError("every section must be an object") birlesik = copy.deepcopy(yerel) for k, v in overrides.items(): birlesik[k] = jev_settings._fill(v, yerel[k]) jev_settings.validate(birlesik) return overrides def cek(settings, key, urlopen=_ACICI.open): """One fetch. Returns {"ok": bool, "overrides": {...}, "error": str}; never raises.""" if not key: return {"ok": False, "error": "Dify key not configured"} url = settings['policy']['endpoint'].rstrip('/') + '/workflows/run' govde = json.dumps({"inputs": {}, "response_mode": "blocking", "user": "aiturk-codex-bridge"}).encode("utf-8") istek = urllib.request.Request(url, data=govde, method="POST", headers={ "Authorization": "Bearer " + key, "Content-Type": "application/json", "User-Agent": UA}) try: with urlopen(istek, timeout=30) as yanit: ham = yanit.read(MAX_BYTES * 4 + 1) if len(ham) > MAX_BYTES * 4: return {"ok": False, "error": "Dify response too large"} veri = json.loads(ham) cikti = ((veri.get("data") or {}).get("outputs") or {}).get("kurallar") if cikti is None: return {"ok": False, "error": "Dify output 'kurallar' missing"} return {"ok": True, "overrides": ayikla(cikti, settings)} except Exception as e: return {"ok": False, "error": f"{type(e).__name__}: {e}"[:300]} def zamani_mi(settings, simdi=None): simdi = time.time() if simdi is None else simdi return settings['policy']['source'] == 'dify' and simdi - _son["zaman"] >= settings['policy']['refreshMinutes'] * 60 def yenile(log=lambda _m: None, urlopen=_ACICI.open, zorla=False): """Fetch when due; store the outcome. Local settings are read without the Dify overlay.""" try: yerel, _ = _yerel_ayar() except Exception as e: log(f"karar kurallari: ayar okunamadi ({type(e).__name__})") return None if not zorla and not zamani_mi(yerel): return None _son["zaman"] = time.time() sonuc = cek(yerel, jev_settings.policy_key(), urlopen=urlopen) kayit = jev_settings.write_policy(sonuc) log("karar kurallari Dify'dan alindi" if sonuc.get("ok") else f"karar kurallari alinamadi: {sonuc.get('error')}" + (" (son gecerli kurallar kullaniliyor)" if kayit.get("ok") else "")) return kayit def _yerel_ayar(): """Settings as written locally (no Dify overlay), so a bad overlay can never validate itself.""" return jev_settings.read(overlay=False)