2929DEFAULT_MAX_CONSECUTIVE_FAILURES = 3
3030DEFAULT_LOW_COST_MODEL = "gpt-5.4-mini"
3131EXECUTION_POLICY_PATH_ENV = "CODEX_AUDIT_SERVICE_EXECUTION_POLICY_PATH"
32+ POLICY_LOAD_ERROR_KEY = "_load_error"
3233QUOTA_STATUS_SEVERITY = {
3334 "ok" : 0 ,
3435 "healthy" : 0 ,
@@ -83,18 +84,32 @@ def _repo_from_run(run: dict[str, Any]) -> str:
8384 return str (metadata .get ("source_repository" ) or metadata .get ("repository" ) or "" )
8485
8586
87+ def _fail_closed_policy (reason : str ) -> dict [str , Any ]:
88+ return {
89+ POLICY_LOAD_ERROR_KEY : reason ,
90+ "default" : {
91+ "max_autonomy" : AUTONOMY_MANUAL ,
92+ "max_consecutive_failures" : 1 ,
93+ },
94+ }
95+
96+
8697def load_execution_policy (path : Path | None = None ) -> dict [str , Any ]:
8798 """Load service-owned execution policy for repo autonomy thresholds."""
8899 if path is None :
89100 configured = os .environ .get (EXECUTION_POLICY_PATH_ENV , "" ).strip ()
90101 if not configured :
91102 return {}
92103 path = Path (configured ).expanduser ()
104+ if not path .exists ():
105+ return _fail_closed_policy ("execution policy file is unavailable" )
93106 try :
94107 payload = json .loads (path .read_text (encoding = "utf-8" ))
95108 except (OSError , json .JSONDecodeError ):
96- return {}
97- return payload if isinstance (payload , dict ) else {}
109+ return _fail_closed_policy ("execution policy file is unreadable" )
110+ if not isinstance (payload , dict ):
111+ return _fail_closed_policy ("execution policy file is invalid" )
112+ return payload
98113
99114
100115def repo_execution_policy (repo : str , policy : dict [str , Any ] | None = None ) -> dict [str , Any ]:
@@ -146,6 +161,7 @@ def decide_automation_execution(
146161) -> dict [str , Any ]:
147162 """Produce a safe execution decision from health, quota, failures, and repo policy."""
148163 repo_policy = repo_execution_policy (repo , policy )
164+ policy_load_error = str ((policy or {}).get (POLICY_LOAD_ERROR_KEY ) or "" ) if isinstance (policy , dict ) else ""
149165 max_autonomy , autonomy_config_error = _parse_autonomy (repo_policy .get ("max_autonomy" ), AUTONOMY_AUTO_PR )
150166 max_failures = _safe_positive_int (repo_policy .get ("max_consecutive_failures" ), DEFAULT_MAX_CONSECUTIVE_FAILURES )
151167 low_cost_model = str (repo_policy .get ("low_cost_model" ) or DEFAULT_LOW_COST_MODEL )
@@ -170,6 +186,8 @@ def decide_automation_execution(
170186 reasons .append (f"repo max autonomy is { max_autonomy } " )
171187 if autonomy_config_error :
172188 reasons .append (autonomy_config_error )
189+ if policy_load_error :
190+ reasons .append (policy_load_error )
173191 if max_autonomy == AUTONOMY_MANUAL :
174192 action = EXECUTION_HUMAN_REVIEW
175193
@@ -199,16 +217,18 @@ def decide_automation_execution(
199217 if quota in {"low" , "constrained" }:
200218 effective_model = effective_model or low_cost_model or recommend_model (0.0 )
201219 if quota_low_behavior == "defer" :
202- action = EXECUTION_DEFER
203- defer = True
220+ if action != EXECUTION_HUMAN_REVIEW :
221+ action = EXECUTION_DEFER
222+ defer = True
204223 effective_mode = MODE_REVIEW_ONLY
205224 human_review_required = True
206225 reasons .append (f"quota status is { quota } ; deferring automation" )
207226 else :
208227 reasons .append (f"quota status is { quota } ; recommending low-cost model" )
209228 elif quota in {"exhausted" , "blocked" }:
210- action = EXECUTION_DEFER
211- defer = True
229+ if action != EXECUTION_HUMAN_REVIEW :
230+ action = EXECUTION_DEFER
231+ defer = True
212232 effective_mode = MODE_REVIEW_ONLY
213233 human_review_required = True
214234 reasons .append (f"quota status is { quota } ; deferring automation" )
0 commit comments