- M7.1-M7.10: Extensible SourceConnector trait, Obsidian/paperless/git/S3 connectors, sync framework, CLI, HTTP endpoints, health monitoring, gate - M3.5.10: Auth integration with Authentik OIDC → Vault token validation - DESIGN.md: Add source connectors architecture, update auth to Authentik/Vault (Kong removed from cluster) - INDEX.md: 75 tasks, 11 gates - Fix all Kong references in M3.5.1 task
5.3 KiB
M7.3 — paperless-ngx connector
| Field | Value |
|---|---|
| Phase | M7 — Source connectors |
| Size | M — 1–3 days |
| Status | ⬜ Not started |
| Flags | — |
| Spec | inlined below |
| Blocks | M7.10 |
| Depends | M7.1 |
Goal
Implement a SourceConnector for paperless-ngx so OCR'd documents, manuals, and
reference PDFs already stored in the cluster's paperless instance become
searchable through the memory service without manual export.
Facts (inlined — no spec read needed)
paperless-ngx is already running in the cluster. Its REST API provides:
GET /api/documents/— paginated list with filtering by tags, document type, correspondent, dates.GET /api/documents/{id}/— full metadata includingcontent(extracted text).GET /api/documents/{id}/download/— original file.GET /api/documents/{id}/preview/— thumbnail.- Authentication via
Authorization: Token <token>header. - Documents have
checksumfield (sha256 of original file).
Tag filtering is the selection mechanism. Not every scanned receipt belongs in the knowledge base. Config specifies which tags to include:
connectors:
- kind: paperless
name: homelab-paperless
config:
base_url: http://paperless-ngx.paperless.svc.cluster.local:8000
token_secret: paperless-api-token # k8s secret ref
tags: [reference, manual, runbook] # only sync these
format: text # use extracted text content
page_size: 100 # API pagination size
Content comes as extracted text. paperless-ngx OCRs documents on import and
stores the text in the content field. Use this directly — no PDF parsing needed
in the connector. The text quality depends on paperless's OCR config.
Checksum enables cheap change detection. paperless provides checksum per
document. The sync framework (M7.6) compares this against the last-known hash
to skip unchanged documents.
Steps
- Implement
PaperlessConnectorinmem-ingest/src/connectors/paperless.rs. list_documents()— paginateGET /api/documents/?tags__name__in=..., extractid,title,checksum,modifiedfor each.fetch_document()—GET /api/documents/{id}/, extractcontentfield, return with metadata (title, tags, correspondent, date_created).health_check()—GET /api/and verify 200; report document count fromGET /api/documents/?tags__name__in=...&page=1&page_size=1(readcount).- Handle pagination (paperless returns
nextURL for subsequent pages). - Token auth from k8s secret (resolve
token_secretto actual token value). - Register
"paperless"kind in connector registry factory. source_type()returnsReference.- Rate limit API calls (configurable, default 10 req/s) to avoid overloading the paperless instance.
Acceptance
PaperlessConnectorimplementsSourceConnectorfully.- Tag filtering limits which documents are listed.
- Pagination handles > 100 documents correctly.
content_hashuses paperless'schecksumfield for change detection.- Auth token resolved from k8s secret (not hardcoded).
- Health check reports document count matching tag filter.
Verify
Harness: mock HTTP server (wiremock or similar) returning paperless API responses; fixture JSON responses for list/detail endpoints.
Integration test — tests/it_paperless_connector.rs:
a1_list_filters_by_tags— mock returns 5 docs, 3 with matching tags; assertlist_documents()returns 3.a2_pagination_fetches_all— mock returns 2 pages of 50; assert 100 docs.a3_fetch_returns_content— mock detail endpoint; assert text matches fixture.a4_fetch_includes_metadata— assert returned metadata includes title, tags, correspondent, date fields.a5_health_check_reachable— mock 200; assertreachable: truewith count.a6_health_check_unreachable— mock connection refused; assertreachable: falsewith error message.a7_checksum_as_content_hash— assertSourceDocument.content_hashis populated from paperlesschecksumfield.a8_auth_header_sent— assert mock receivedAuthorization: Token <value>.a9_config_from_yaml— parse connector from YAML fixture; assert fields match.a10_live—#[ignore]; real paperless instance; list + fetch one doc; print title and content length for human sanity check.
Command: cargo test --test it_paperless_connector (add -- --ignored for a10)
False pass:
- Mocking without verifying auth header. A connector that works in tests but sends no auth fails silently against real paperless.
- Testing single page only. Pagination bugs are invisible with <
page_sizedocs.
Traps
- Assuming
contentis always populated. paperless may have documents without OCR text (e.g., empty scans). Return empty content with a warning, don't panic. - Hardcoding the base URL without trailing-slash normalization.
/api/documents/vs/api/documentsbehaves differently. - Not handling paperless API rate limits (429 responses). Add retry-after logic.
- Resolving k8s secrets at config parse time. Defer to runtime — secret may not exist in dev/test environments. Use env var fallback.
Background: DESIGN.md — source connectors, paperless-ngx integration