haiku.rag/tests/ingester
Yiorgis Gozadinos 4aee18dcbe
Operator dashboard at GET /; tighten Logfire span shape
Self-contained HTML status page served from the ingester's FastAPI app.
Polls /health, /sources, /stats, /jobs?status={claimed,dead,succeeded}
every 3s from the browser and renders queue chips, sources with
last-poll/skip-reason/circuit state, active jobs with cancel, recent
failures with retry, and recently-completed feed with op badges so
DELETE rows are visually distinct from UPSERTs. Zero external deps —
single static HTML, no CDN, no fonts, no images. Works offline.

To support the dashboard:
- New /stats endpoint exposing rolling throughput (5m/30m/1h), worker
  occupancy, oldest-queued age, and per-source DLQ + queue-depth
  breakdowns. Each field is a single SQL aggregation against the queue.
- JobRepo gains count_succeeded_since, oldest_queued_age_seconds,
  counts_by_source.
- SourceSummary gains last_skip_reason. BasePoller now records the
  reason the most recent sweep attempt was skipped ("pending_work" /
  "circuit_open"), cleared on the next successful poll. Closes the
  gap where operators couldn't tell from /sources alone why a source
  wasn't picking up new work.

Auth: dashboard route is unauthenticated (markup only). The JS attaches
the bearer to its own JSON fetches; on 401 it prompts once and stashes
the token in localStorage.

Two Logfire fixes that landed alongside:

- Drop logfire.instrument_fastapi() and the [fastapi] extra. The control
  plane is polled frequently (dashboard + docker healthcheck), so every
  endpoint became a span and drowned the useful traces. logfire itself
  stays — pulled in transitively via pydantic-ai-slim[logfire] — so
  ingester.poller.* / ingester.job / document.* spans keep emitting.

- Wrap FSPoller._handle_watch_change in an ingester.poller.watch_event
  span and pass _enqueue_extra. Without this, the watchfiles callback
  ran with no active context, the _otel carrier in job.extra was empty,
  and the worker's ingester.job span surfaced as an orphan trace root
  instead of nesting under the FS event that caused it.
2026-05-26 11:44:47 +03:00
..
__init__.py ingester: add source-adapter scaffolding + FS source 2026-05-26 11:41:53 +03:00
test_api.py Operator dashboard at GET /; tighten Logfire span shape 2026-05-26 11:44:47 +03:00
test_circuit_breaker.py additional config, pollers, serve 2026-05-26 11:41:54 +03:00
test_config.py Handle WebDAV as source 2026-05-26 11:43:30 +03:00
test_fs_source.py drop the old monitor, rename serve→mcp, add e2e tests 2026-05-26 11:42:41 +03:00
test_http_source.py HTTPSource.head() returns ETag for the revision short-circuit 2026-05-26 11:44:45 +03:00
test_pipeline.py typed UnsupportedSourceError replaces pipeline string-marker matching 2026-05-26 11:44:46 +03:00
test_pollers.py Operator dashboard at GET /; tighten Logfire span shape 2026-05-26 11:44:47 +03:00
test_queue.py Operator dashboard at GET /; tighten Logfire span shape 2026-05-26 11:44:47 +03:00
test_resolve_fetcher.py add HTTP and S3 source adapters + resolve_fetcher 2026-05-26 11:41:53 +03:00
test_revision_round_trip.py record cassettes against new fixtures, fix stale assertions 2026-05-26 11:44:46 +03:00
test_s3_source.py canonical metadata keys: source_revision + content_type, bump to 0.50.0 2026-05-26 11:43:30 +03:00
test_serve_integration.py canonical metadata keys: source_revision + content_type, bump to 0.50.0 2026-05-26 11:43:30 +03:00
test_sources_base.py route create_document_from_source through source adapters 2026-05-26 11:41:54 +03:00
test_webdav_source.py regex-based _strip_etag with weak-marker test 2026-05-26 11:44:45 +03:00
test_workers.py Release claim on jobs when cancelled 2026-05-26 11:44:46 +03:00