fix(pipeline): add idempotency guard to submit-pass0b endpoint

Prevents duplicate batch submissions that caused ~$170 in extra costs.
Refuses new submit if a batch was submitted in the last 10 minutes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-04-27 18:59:03 +02:00
parent 28aa74b4b0
commit e6e2688b56

View File

@@ -2298,13 +2298,29 @@ class SubmitPass0bRequest(BaseModel):
batch_size: int = 5 batch_size: int = 5
_last_submit_batch_id: str = ""
_last_submit_time: float = 0
@router.post("/generate/submit-pass0b") @router.post("/generate/submit-pass0b")
async def submit_pass0b(req: SubmitPass0bRequest): async def submit_pass0b(req: SubmitPass0bRequest):
"""Submit Pass 0b batch to Anthropic Batch API. """Submit Pass 0b batch to Anthropic Batch API.
Loads unprocessed obligations, applies pre-LLM filter, submits batch. SAFETY: Refuses to submit if a batch was submitted in the last 10 minutes.
Returns batch_id for status polling and later result processing. 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 from services.decomposition_pass import DecompositionPass
db = SessionLocal() db = SessionLocal()
try: try:
@@ -2313,6 +2329,12 @@ async def submit_pass0b(req: SubmitPass0bRequest):
limit=req.limit, limit=req.limit,
batch_size=req.batch_size, 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 return result
except Exception as e: except Exception as e:
logger.error("Submit Pass 0b failed: %s", e) logger.error("Submit Pass 0b failed: %s", e)