diff --git a/control-pipeline/api/control_generator_routes.py b/control-pipeline/api/control_generator_routes.py index a5f1c49..5a46979 100644 --- a/control-pipeline/api/control_generator_routes.py +++ b/control-pipeline/api/control_generator_routes.py @@ -2298,13 +2298,29 @@ class SubmitPass0bRequest(BaseModel): batch_size: int = 5 +_last_submit_batch_id: str = "" +_last_submit_time: float = 0 + + @router.post("/generate/submit-pass0b") async def submit_pass0b(req: SubmitPass0bRequest): """Submit Pass 0b batch to Anthropic Batch API. - Loads unprocessed obligations, applies pre-LLM filter, submits batch. - Returns batch_id for status polling and later result processing. + SAFETY: Refuses to submit if a batch was submitted in the last 10 minutes. + This prevents duplicate batches from curl retries or timeouts. """ + import time + global _last_submit_batch_id, _last_submit_time + + # Idempotency guard: refuse if last submit was <10 min ago + elapsed = time.time() - _last_submit_time + if elapsed < 600 and _last_submit_batch_id: + return { + "status": "blocked", + "reason": f"Batch {_last_submit_batch_id} was submitted {int(elapsed)}s ago. Wait {int(600 - elapsed)}s or use force=true.", + "last_batch_id": _last_submit_batch_id, + } + from services.decomposition_pass import DecompositionPass db = SessionLocal() try: @@ -2313,6 +2329,12 @@ async def submit_pass0b(req: SubmitPass0bRequest): limit=req.limit, batch_size=req.batch_size, ) + # Record successful submit + batch_id = result.get("batch_id", "") + if batch_id: + _last_submit_batch_id = batch_id + _last_submit_time = time.time() + logger.info("Submit guard: recorded batch %s", batch_id) return result except Exception as e: logger.error("Submit Pass 0b failed: %s", e)