Error Handling¶
Exception types¶
PacError¶
Defined per-domain:
Raised when Alanube returns a non-success status that we can't recover from. Carries the HTTP status and Alanube's error message.
MissingRetentionCodeError¶
Raised by InvoicePacService when an invoice for a retention-eligible client lacks a retention code. Lookup order is: invoice override → client default → error.
DGI / file-import exceptions¶
app/wrappers/dgi/dgi_exceptions.py
Used by the createInvoiceFromFile flow:
QrCodeNotFoundError— uploaded file had no QR.QrCodeNotReadyError— QR present but not yet processed.QrCodeUrlNotValidError— QR points at something invalid.UnknownQrCodeError,UnknownAiError— fallthrough cases.
Retry policy¶
| Where | Attempts | Backoff | Retry on |
|---|---|---|---|
PacService HTTP methods |
2 | 2s → 10s exponential | aiohttp.ClientResponseError |
RucVerificationService.verify_ruc |
2 | 2s → 10s exponential | aiohttp.ClientError |
pac_status_poll_task |
60 polls × 5s | fixed 5s | always (until terminal status) |
There is no broker-level retry for taskiq tasks beyond what the task itself implements.
Where errors get logged¶
Loguru is the logger throughout the codebase. The PAC code uses these conventions:
logger.info(...)— successful submissions, PDF attachments, status changes.logger.warning(...)— recoverable oddities (missing PDF URL, polling timeout).logger.error(...)— Alanube returned a 4xx/5xx with a body we want to keep.logger.exception(...)— uncaught exception, includes traceback.
In production, logs are shipped to Logfire (see logfire deps in pyproject.toml). Filter by service.name=cifras-backend and search for "PAC" to see PAC-specific events.
What gets persisted on failure¶
| Failure | Are inputs saved? | pac_response saved? |
legal_status set? |
|---|---|---|---|
| RUC verification failure | No | No | No (transaction rolled back) |
| Missing retention code | No | No | No |
POST /invoices 4xx |
Depends on call site | Sometimes | Sometimes (PAC_REJECTED) |
| Network error after retries | No | No | No |
| Webhook signature mismatch | n/a | n/a | n/a (rejected before any DB work) |
| Webhook for unknown doc | n/a | n/a | n/a (logged, returned 200) |
When debugging a "this invoice was supposed to go through but didn't", the audit trail is pac_input + pac_response JSONB columns. They're often the only record of what happened.
No dead-letter queue (yet)¶
If a polling task crashes mid-loop, the document state is left wherever it was. There is no automatic re-enqueue. Manual recovery via attachPacPdf is the current workaround. This is a known gap — see IDEAS.md for any backlog notes.