Commit Graph
7 Commits
Author SHA1 Message Date
Chris CoutinhoandClaude Opus 4.8 2f8875e736 fix(document): timeout reason bucket + Sonar https hotspot (#892 round 2)
Round-2 review on PR #892:
- OcrProcessor.process now catches TimeoutError separately and returns
  parse_failed_reason="timeout" with a populated message ("OCR timed out after
  Ns"), instead of conflating timeouts with API errors under "error" and logging
  an empty suffix. Lets dashboards tell a too-low timeout from a failing
  provider. Test added.
- Add validator-rejection tests for DOCUMENT_OCR_TIMEOUT_SECONDS=0 (gte=1) and
  DOCUMENT_MAX_PDF_SIZE_MB=-1 (gte=0), matching the existing validator-test
  pattern.
- Comment the _Settings test fixture's max_pdf_size_mb=0.0 default.

SonarCloud: quality gate was failing on new_security_hotspots_reviewed (S5332
"use https") from an http:// URL in the new gateway-timeout test — switched to
https:// (mirrors commit 98c9d58e).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-11 06:12:21 +02:00
Chris CoutinhoandClaude Opus 4.8 64ea5c8631 fix(document): apply OCR timeout to Mistral backend + review/Sonar fixes (#892)
Round-1 review on PR #892:
- Wire DOCUMENT_OCR_TIMEOUT_SECONDS into _MistralOcrBackend too (was
  gateway-only): wrap process_async in anyio.fail_after so the SDK-managed
  client honours the setting; on expiry it fails fast as a clean parse error.
  Test added.
- Tighten the misleading "honoured without a restart" comment — per-call
  get_settings() is for test monkeypatching; a live change still needs a
  restart since the backend is cached for the pod lifetime.
- Comment the size guard's two intentional gaps: an explicit processor_name
  override bypasses it, and the early return skips the parse-duration histogram.

SonarCloud (new-code smells in the added tests):
- S1244 float-equality asserts → pytest.approx (test_config.py, test_ocr_processor.py).
- S1186/S7503: rewrite the gateway-timeout test with mocker AsyncMock/MagicMock
  instead of a hand-rolled fake client (no empty method, no async-without-await).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-11 05:36:47 +02:00
Chris CoutinhoandClaude Opus 4.8 523e4cb7b5 feat(document): configurable OCR timeout and fail-fast PDF size guard
Two ingest-robustness fixes from card 309 (OHR-Bench smoke-test triage).

The OCR backend timeout was a hardcoded 180s module constant, so a tenant
whose gateway has its own shorter ceiling couldn't tune it. Promote it to
DOCUMENT_OCR_TIMEOUT_SECONDS (default 180), resolved per call via get_settings
so an override applies without a restart.

Large, awkward PDFs (e.g. a 42 MB scanned DUDE) were handed straight to the
fast/OCR tiers, where they burned the full OCR timeout for zero recovered
text. Add a pre-parse size guard in the tiered PDF pipeline: a PDF over
DOCUMENT_MAX_PDF_SIZE_MB (default 50, 0 disables) fails fast with
parse_failed_reason="oversize" before any tier runs, so the existing
permanent-failure path marks the placeholder failed and records
astrolabe_document_parse_failed_total{reason="oversize"} instead of retrying.

Both knobs go through Settings + dynaconf validators (env-var keys verified by
regression tests) and are documented under Background Indexing Configuration.

Refs: Deck board 12 card 309 (AC #3 OCR timeout + size guard).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-11 05:09:58 +02:00
Chris CoutinhoandClaude Opus 4.8 bc957b5bc7 fix(review): _enum_fields validation, gate classify_from_text flags, OCR warnings
Address PR #858 review round 3:

- document_tier1_engine / document_ocr_provider now validate + normalize via
  Settings.__post_init__ _enum_fields (the repo's canonical opt-in-enum pattern;
  case-insensitive) instead of dynaconf Validators. A typo now raises ValueError
  at load and "Gateway" normalizes to "gateway".
- classify_from_text gates no_text_layer/bad_text_layer on ocr_frac >=
  OCR_PAGE_FRACTION, matching classify_pdf -- a "fast"-routed doc with a few junk
  pages no longer emits a misleading flag (keeps the shadow vs hot-path
  classification metrics consistent).
- build_ocr_backend warns when an EXPLICIT provider is misconfigured
  (gateway without EMBEDDING_GATEWAY_URL, mistral without MISTRAL_API_KEY)
  instead of silently returning None.
- Pypdfium2FastProcessor.health_check probes the import; documented why
  OcrProcessor.health_check is unconditionally True (lazy per-tenant backends).
- Removed the leftover per-boundary / per-chunk debug logging loops.

Tests: enum normalization + rejection for the two new settings.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 02:35:26 +02:00
Chris CoutinhoandClaude Opus 4.8 f1272dfe84 fix(review): lock OCR backend init, warn on rollback fallthrough, zero-page metric
Address PR #858 review round 2:

- OcrProcessor backend resolution is now guarded by an anyio.Lock (lazy-init,
  double-checked) so a burst of concurrent first-OCR calls resolves the backend
  once instead of each fetching its own gateway M2M token.
- The document_tier1_engine=pymupdf rollback now logs a warning when it falls
  back to the fast processor (no 'structured' registered) instead of silently
  using the very engine the operator opted out of.
- classify_from_text defaults ocr_frac to 0.0 (not 1.0) for a zero-page PDF, so
  the recorded classification metric is "fast" (no OCR evidence) rather than a
  misleading "ocr"; the no_text_layer/bad_text_layer flags are gated on having
  sampled at least one page.

New tests: zero-page classify routes fast, rollback-fallback warning.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 02:24:08 +02:00
Chris CoutinhoandClaude Opus 4.8 1634e8adc2 fix(review): cache OCR backend, drop asserts, real pipeline_tier, guard zero-page
Address PR #858 review:

- 🔴 OcrProcessor now resolves its backend once and reuses it. Rebuilding per
  call created a fresh GatewayTokenProvider each time -- discarding its M2M-token
  cache, so every OCR'd document fetched a new token -- and a new Mistral client.
- 🔴 build_ocr_backend uses explicit ValueError (not assert, which is stripped
  under `python -O`) for the gateway M2M triple.
- PIPELINE_TIER in the Qdrant payload now reflects the tier that actually
  produced the doc: the registry stamps result.metadata["pipeline_tier"] and the
  processor reads it (was hardcoded "fast", wrong for OCR/structured).
- Escalation now requires classification.page_count > 0, so a zero-page
  (empty/corrupt) PDF isn't pointlessly sent to OCR; documented that a fast
  FAILURE (encrypted/unopenable) is a hard failure and is not OCR-escalated.
- Documented the OCR page_boundaries separator-attribution choice.
- Downgraded the per-document page-boundary / page-assignment INFO logs to debug.

New tests: zero-page no-escalation, pipeline_tier stamping.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 02:13:09 +02:00
Chris CoutinhoandClaude Opus 4.8 3bd1b46d9c feat: tier-3 OCR processor (gateway or direct Mistral)
Adds the OCR escalation target the tiered registry already routes to. Scanned /
no-text-layer PDFs (the tier-0 "ocr" verdict) escalate here when
document_ocr_enabled (default off).

Two interchangeable backends, selected by document_ocr_provider
(auto | gateway | mistral | none):
- gateway: POST to the Astrolabe Cloud model gateway's /v1/ocr -- the same
  M2M-authenticated gateway as embeddings, so NO provider keys live in the pod
  (the platform default; reuses EMBEDDING_GATEWAY_URL + the M2M creds).
- mistral: call the Mistral OCR API directly from the pod (MISTRAL_API_KEY), for
  self-hosters / deployments without the gateway.
"auto" prefers the gateway, then direct Mistral.

Both return per-page markdown joined into text + exact page_boundaries (the
pdf_highlighter contract; bbox re-derived from the PDF bytes as for other tiers).
Validated end-to-end via direct Mistral on the scanned Student 147.pdf:
success, 15 pages, 22k chars, offsets exact, ~4s.

Settings: document_ocr_provider (enum-validated), document_ocr_model
("mistral/mistral-ocr-latest" -- gateway routes on the prefix, the direct mistral
backend strips it). OcrProcessor registered at lowest priority so it is never the
non-tiered default.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 01:42:35 +02:00