- job_cleanup_loop (src/jobs/job_manager.py): hourly in-process pass over
JobManager.cleanup_expired_jobs, started at app startup and cancelled
at shutdown; Redis job payloads auto-expire but set memberships do not.
- docs/scheduler-tasks.md: the four production Scheduler task payloads
for the deploy checklist - nightly integrity check 04:30, weekly
quality report Sunday 03:00 (day_of_week=6, 0=Monday), daily Paperless
orphan-cleanup 05:00 hitting the existing
/maintenance/cleanup/paperless?user=jpmschweitzer&dry_run=false
endpoint, and disabling test_example_task - with exact HTTP bodies
(explicit user=jpmschweitzer, Authorization: Bearer ${LIBRARY_API_KEY}
placeholder).
- scripts/register_scheduler_tasks.py: reads SCHEDULER_URL from env,
DRY-RUN BY DEFAULT (prints the exact payloads, provably contacts
nothing), --execute gated and requiring LIBRARY_API_KEY to fill the
placeholder. NOT executed - definitions delivered for the deploy
checklist only.
9 new offline tests (loop passes/error-resilience/cancellation, payload
schedules, explicit production user, placeholder, dry-run default).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QbFZyDvYksazX6nYQYZ67L
Replace the four stub endpoints with real implementations, all requiring
an explicit tenant user (Phase B rule):
- /ingest/check-updates: GraphService now records a SHA-256 content_hash
on every Document node at ingestion time; the endpoint compares those
stored hashes against current Wiki.js page content in one UNWIND Cypher
query per tenant and returns changed/new/deleted page lists (entity-stub
pages excluded, pre-hash-tracking documents flagged stored_hash_missing).
- /ingest/status/{job_id}: backed by the Redis JobManager; jobs are
tenant-scoped (foreign jobs 404). /ingest/page, /ingest/batch and
/ingest/all now create job records and return job_id.
- /ingest/repo-status/{repository}: wiki page count vs indexed Document
nodes under users/{tenant}/{repository} plus tenant job stats.
- /deduplicate/check: tenant-scoped Qdrant similarity scan; chunk pairs
above ~0.9 cosine from different pages grouped per page pair with best
score and page references (read-only).
Supporting changes: get_job_manager dependency (+ shutdown close),
scroll_all_points can return vectors, VectorService.find_duplicate_pairs,
src/core/hashing.compute_content_hash. 13 new offline unit tests.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QbFZyDvYksazX6nYQYZ67L
/query/graph scoping was a documented no-op (graph_service returned the
query unscoped) and neo4j_client permitted writes; a live probe showed a
nonexistent user could read the whole graph.
- Add Neo4jClient.execute_read() that opens the session with
default_access_mode=READ_ACCESS so the database refuses writes even if
validation is bypassed.
- GraphService.execute_query() now rejects queries containing
CREATE/MERGE/DELETE/DETACH/SET/REMOVE/DROP/FOREACH/LOAD or any CALL
(conservative word-boundary denylist on the uppercased query) and
executes through the read-only session; the no-op _scope_query_to_user
is removed.
- Remove the false user-scoping claims from /query/graph (main.py) and
/graph/query docs and the CypherQueryRequest model: the endpoints are
documented as admin/debug, unscoped read-only (per-tenant label
injection for arbitrary Cypher would need a real parser; /graph/nodes
remains the tenant-scoped path).
- Offline unit tests: denylist coverage (incl. lowercase/multiline/CALL),
word-boundary false-positive check, and READ_ACCESS session assertion.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Remove the implicit jpmschweitzer default tenant (DEFAULT_USER) from
src/core/multi_tenancy.py and every endpoint and request model that
inherited it (~40 endpoints across /query, /wiki, /vector, /graph,
/ingest, /volatile, /documents, /stats, /rag).
- Add validate_required_user() + RequiredUser pydantic type in
multi_tenancy and a shared require_user FastAPI dependency
(RequiredUserQuery) that rejects missing, empty, and whitespace-only
users with 422, following the /maintenance/* pattern.
- Wiki page create / smart-create / dossier request models now require
user (no fallback in wiki_service).
- /maintenance/cleanup/test-data derives the tenant from the page path
instead of using the production tenant collection.
- Wiki.js change listener skips changes when no tenant user can be
derived from the notification email instead of defaulting to the
production tenant.
- Consolidation service internal helpers no longer default to the
production tenant.
- Tool catalog marks user as required with honest descriptions.
- OpenAPI descriptions updated honestly; CHANGELOG notes that callers
(tatlock, Scheduler ingest tasks) must now send explicit user.
- Offline tests: 422 coverage for query/body endpoints, required-user
validator tests; updated legacy tests that assumed a default tenant.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- /stats passed the bare user name as path prefix (matched nothing, always
reported 0 of 138 pages); it now counts pages under users/{user}
- list_pages applied the API-side limit before client-side path/tag filters,
dropping matching pages that sort late; limit now applies after filtering
- list_all_pages replaced the fake while/break pagination with a real
limit-growth loop: Wiki.js 2.x pages.list supports only a limit argument
(no offset - verified via GraphQL introspection), so the client doubles
the limit until the API returns fewer pages than requested
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The deployed container sets OLLAMA_MODEL=nomic-embed-text for embeddings,
which shadowed the generation-model setting and broke Phase 0 keyword
extraction and Phase 4 LLM re-ranking on every request. The setting is now
ollama_llm_model (env: OLLAMA_LLM_MODEL, default gemma4:e2b), startup logs
the resolved generation model, and Phase 0/Phase 4 LLM calls are wrapped in
a 12s asyncio.wait_for with graceful fallback so a hung call cannot gate
retrieval for the full 120s client timeout.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Add /documents router with webhook, upload, search, health endpoints
- Create DocumentSyncService for indexing documents to vectors/graph
- Add PaperlessClient for REST API integration
- Configure dependency injection for Paperless client
- Add document models for webhook payloads and responses
- Event-driven architecture via Paperless workflow webhooks
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Complete maintenance subsystem for index health and cleanup:
Endpoints:
- GET /maintenance/health - lightweight (or detailed) health check
- POST /maintenance/cleanup/all - full orphan cleanup
- POST /maintenance/cleanup/vectors - purge orphan vector chunks
- POST /maintenance/cleanup/graph - purge orphan graph nodes
- POST /maintenance/reconcile-index - cleanup + reindex missing pages
Bidirectional orphan detection:
- find_documents_without_vectors() in GraphService
- find_chunks_without_graph_nodes() in VectorService
Redis integration:
- Tracks last_cleanup timestamp for scheduler visibility
Config additions:
- Document store, volatile cache, and maintenance settings
- VectorServiceDep and GraphServiceDep type aliases
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add /rag/search endpoint for web, news, and image search via SearXNG
- Add /content/extract and /content/extract/batch endpoints
- Add ContentExtractor client using Trafilatura for content extraction
- Enhance HybridRAG web search with full content extraction
- Add Redis caching for search results
- Add new configuration options for search and extraction timeouts
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add pyproject.toml with project metadata and version (1.1.0)
- Update config.py to read version from pyproject.toml
- Update main.py to use centralized version in FastAPI app
- Add CHANGELOG.md documenting v1.0.0 and v1.1.0 changes
Version is now the single source of truth in pyproject.toml and is
displayed in the health check endpoint and API docs.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>