Initial curated RAGcore source from c16667f0c9397e2bfc8dd0255b114fba23c8d05e

This commit is contained in:
Public Source Publisher committed 2026-10-01 01:34:21 +02:00
commit 7ca726caf2
659 files changed
+98171

No files matched your search

@@ -0,0 +1,632 @@
"""Integration gate: the real ``IngestUploadHandler`` against a real Qdrant
server, searched through the real ``RetrievalPipeline``/``ProductionSearchExecutor``.
(This is the *handler-level* integration gate. The system-level gate that
goes through HTTP upload -> PostgreSQL job -> worker lease -> this handler ->
HTTP search lives in ``tests/system/``.)
upload payload -> security gate -> parser -> chunker -> dense embedding
-> sparse encoding -> Qdrant upsert -> /v1/search execution path
Proves, against the live index, that:
1. the document is processed and indexed;
2. every stored point carries a dense vector;
3. every stored point carries a sparse vector;
4. the document is retrievable via dense search (exact-vector query);
5. the document is retrievable via sparse search (identifier-token query);
6. RRF fuses both backends (both ranks present, no degradation);
7. language / document_id filters constrain the results;
8. **source identity**: the source UUID the *caller* supplied at upload time
-- never a value read back from the index -- is what the ``source_ids``
filter matches and what every candidate's provenance reports: source A's
two documents come back for ``source_ids=[A]``, source B's one document
for ``[B]``, neither leaks into the other, and an ad hoc upload without a
source id belongs to the space's built-in Uploads source.
Embeddings use a deterministic hash-based mock transport: the point of this
gate is the indexing/search contract, not embedding quality, and determinism
is what makes the dense exact-match assertion (cosine similarity 1.0 with an
identical query vector) a real proof that dense retrieval works end to end.
"""
from __future__ import annotations
import asyncio
import hashlib
import json
import math
import os
from collections.abc import AsyncIterator
from dataclasses import replace
from datetime import UTC, datetime
from pathlib import Path
from uuid import UUID, uuid4
import httpx
import pytest
from qdrant_client import AsyncQdrantClient
from ragcore.api.v1.search.service import (
SearchExecutionRequest,
SearchFilterData,
SearchQuery,
)
from ragcore.application.documents import (
DocumentRecord,
DocumentVersionRecord,
DocumentVersionState,
)
from ragcore.application.jobs.models import Job, JobContext
from ragcore.application.retrieval.authorization import AuthorizedRetrievalScope
from ragcore.application.sources import SourceRecord
from ragcore.config import Settings
from ragcore.domain.authorization import Principal, PrincipalType
from ragcore.domain.control import DataPolicy
from ragcore.domain.models import EmbeddingProfile, EmbeddingProvider
from ragcore.domain.sources import upload_source_id
from ragcore.infrastructure.blob.filesystem import FilesystemBlobStorage
from ragcore.infrastructure.ingestion.handler import IngestUploadHandler
from ragcore.infrastructure.models.embedding_factory import create_embedding_adapter_factory
from ragcore.infrastructure.models.persistence import JsonEmbeddingProfileStore
from ragcore.infrastructure.parsers.publisher import FilesystemArtifactPublisher
from ragcore.infrastructure.qdrant.rebuild import QdrantRebuildStore
from ragcore.infrastructure.retrieval.production_executor import (
ProductionSearchExecutor,
RetrievalPipeline,
)
from ragcore.infrastructure.security import (
ContentSecurityGateService,
production_content_security_policy,
)
WORKSPACE_ID = uuid4()
SPACE_ID = uuid4()
SOURCE_A = uuid4()
SOURCE_B = uuid4()
DIMENSIONS = 8
ENGLISH_DOCUMENT = (
"Gateway Failover Runbook\n\n"
"When the payment gateway loses power, cycle breaker BRK-4417 in rack two.\n"
"Wait ten seconds before restoring the feed and confirm the status light.\n"
)
DUTCH_DOCUMENT = (
"Firewall Segmentatie Handleiding\n\n"
"Dit document beschrijft de netwerksegmentatie van de firewall voor het\n"
"productienetwerk. De regels worden per zone beheerd en moeten na iedere\n"
"wijziging opnieuw worden gecontroleerd door een beheerder van het team.\n"
)
SOURCE_B_DOCUMENT = (
"Certificate Rotation Procedure\n\n"
"Rotate the edge certificate CERT-8820 every ninety days using the vault job.\n"
"Confirm the fingerprint on the load balancer after the rotation completes.\n"
)
AD_HOC_DOCUMENT = (
"Lunch Menu\n\nThursday soup SOUP-001 is tomato basil, served from noon in the canteen.\n"
)
def _deterministic_vector(text: str) -> list[float]:
digest = hashlib.sha256(text.encode("utf-8")).digest()
raw = [digest[index] / 255.0 - 0.5 for index in range(DIMENSIONS)]
norm = math.sqrt(sum(value * value for value in raw)) or 1.0
return [value / norm for value in raw]
def _embedding_transport() -> httpx.MockTransport:
def handler(request: httpx.Request) -> httpx.Response:
body = json.loads(request.content)
vectors = [_deterministic_vector(text) for text in body["input"]]
return httpx.Response(200, json={"embeddings": vectors})
return httpx.MockTransport(handler)
class MemoryDocumentRepository:
"""Just enough of DocumentRepository for the handler's happy path."""
def __init__(self) -> None:
self.documents: list[DocumentRecord] = []
self.versions: dict[UUID, DocumentVersionRecord] = {}
self.published: list[UUID] = []
async def create_document(self, document: DocumentRecord) -> DocumentRecord:
self.documents.append(document)
return document
async def create_version(self, version: DocumentVersionRecord) -> DocumentVersionRecord:
self.versions[version.id] = version
return version
async def get_version(self, version_id: UUID) -> DocumentVersionRecord | None:
return self.versions.get(version_id)
async def get_document_by_source(
self, workspace_id: UUID, source_key: str, external_id: str
) -> DocumentRecord | None:
return next(
(
document
for document in self.documents
if document.workspace_id == workspace_id
and document.source_key == source_key
and document.external_id == external_id
),
None,
)
async def publish_validated(self, version_id, artifacts, chunks): # type: ignore[no-untyped-def]
self.versions[version_id] = replace(
self.versions[version_id], state=DocumentVersionState.PUBLISHED
)
self.published.append(version_id)
return self.versions[version_id]
async def reactivate_source(self, document_id: UUID) -> None:
raise AssertionError("not exercised by this gate")
class MemorySourceRegistry:
"""Enough of ``SourceRegistry`` for ingestion that no sync run owns.
The handler reports every finished ingest back to the registry so a sync
waiting on its children can settle. These uploads have no remembered item,
so both marks legitimately match nothing and the convergence query is
never reached -- which is exactly the branch this gate should exercise.
The marks used to be absent altogether. The handler's reporting step
catches broadly (a lost convergence signal must never mask an ingest
result), so the missing methods raised ``AttributeError`` into that catch
and the gate quietly ran the failure path on every document while looking
green.
"""
def __init__(self) -> None:
self.records: dict[UUID, SourceRecord] = {}
self.marks: list[tuple[str, UUID, str]] = []
async def ensure_source(self, record: SourceRecord) -> SourceRecord:
return self.records.setdefault(record.id, record)
async def mark_item_ingested(
self,
source_id: UUID,
external_id: str,
*,
fingerprint: str,
document_id: UUID | None,
now: datetime,
) -> bool:
self.marks.append(("ingested", source_id, external_id))
return False
async def mark_item_failed(
self,
source_id: UUID,
external_id: str,
*,
fingerprint: str,
failure_code: str,
retryable: bool,
now: datetime,
job_id: UUID | None = None,
) -> bool:
self.marks.append(("failed", source_id, external_id))
return False
async def summarize_items(self, source_id: UUID) -> object:
raise AssertionError("no remembered item matched, so nothing should be summarized")
async def converge_sync_state(self, source_id: UUID, state: object, **kwargs: object) -> None:
raise AssertionError("no remembered item matched, so no source should be settled")
class NullAudit:
async def record(self, **event: object) -> None:
return None
@pytest.fixture
async def qdrant_client() -> AsyncIterator[AsyncQdrantClient]:
client = AsyncQdrantClient(
url=os.getenv("RAGCORE_QDRANT_URL", "http://qdrant:6333"),
api_key=os.getenv("RAGCORE_QDRANT_API_KEY", "replace-me"),
timeout=60,
)
yield client
await client.close()
async def _active_profile_store(tmp_path: Path, profile_name: str) -> JsonEmbeddingProfileStore:
store = JsonEmbeddingProfileStore(tmp_path / "profiles.json")
profile = EmbeddingProfile(
id=uuid4(),
profile=profile_name,
version="v1",
provider=EmbeddingProvider.OLLAMA,
model_identifier="gate-embed:v1",
model_digest="b" * 64,
dimensions=DIMENSIONS,
context_limit=2048,
tokenizer="gate",
data_policy=DataPolicy.LOCAL_ONLY,
)
await store.add(profile)
await store.activate(profile.id)
return store
def _job_context(payload: dict[str, object]) -> JobContext:
return JobContext(
job=Job(
id=uuid4(),
job_key=f"gate-{uuid4().hex}",
operation="ingest_upload",
target_key="gate",
profile_version="upload-v1",
payload=payload,
available_at=datetime.now(UTC),
),
attempt_number=1,
idempotency_key=uuid4().hex,
cancellation=asyncio.Event(),
)
def _build_handler(
tmp_path: Path,
*,
profile_store: JsonEmbeddingProfileStore,
repository: MemoryDocumentRepository,
registry: MemorySourceRegistry,
embedding_client: httpx.AsyncClient,
qdrant_client: AsyncQdrantClient,
) -> IngestUploadHandler:
return IngestUploadHandler(
document_repository=repository, # type: ignore[arg-type]
blob_storage=FilesystemBlobStorage(tmp_path / "blobs"),
artifact_publisher=FilesystemArtifactPublisher(tmp_path / "parsed"),
embedding_profile_store=profile_store,
embedding_adapter_factory=create_embedding_adapter_factory(
settings=Settings(env="test", ollama_url="http://ollama:11434"),
http_client=embedding_client,
),
qdrant_store=QdrantRebuildStore(qdrant_client),
source_registry=registry,
security_gate=ContentSecurityGateService(
NullAudit(), policy=production_content_security_policy()
),
)
async def _ingest(
handler: IngestUploadHandler,
blob_storage: FilesystemBlobStorage,
*,
content: bytes,
name: str,
source_id: UUID | None = None,
external_id: str | None = None,
locale: str | None = None,
media_type: str = "text/plain",
) -> None:
async def _bytes() -> AsyncIterator[bytes]:
yield content
reference = await blob_storage.put(_bytes(), media_type=media_type, original_name=name)
payload: dict[str, object] = {
"blob_digest": reference.digest,
"media_type": media_type,
"original_name": name,
"space_id": str(SPACE_ID),
"workspace_id": str(WORKSPACE_ID),
}
if source_id is not None:
payload["source_id"] = str(source_id)
payload["external_id"] = external_id or name
if locale is not None:
payload["locale"] = locale
await handler(_job_context(payload))
def _search_request(
query: str, *, filters: SearchFilterData | None = None
) -> SearchExecutionRequest:
return SearchExecutionRequest(
principal=Principal(uuid4(), WORKSPACE_ID, PrincipalType.HUMAN, frozenset()),
query=SearchQuery(query=query, max_results=10, filters=filters or SearchFilterData()),
scope=AuthorizedRetrievalScope(WORKSPACE_ID, frozenset({SPACE_ID})),
retrieval_run_id=uuid4(),
)
class _TitleLookup:
async def titles(self, document_ids: set[UUID]) -> dict[UUID, str]:
return dict.fromkeys(document_ids, "Gate Document")
def _doc_ids(result) -> set[UUID]: # type: ignore[no-untyped-def]
return {hit.citation.document_id for hit in result.hits}
@pytest.mark.integration
@pytest.mark.asyncio
async def test_upload_to_hybrid_search_gate(
tmp_path: Path, qdrant_client: AsyncQdrantClient
) -> None:
profile_name = f"gate-{uuid4().hex[:10]}"
profile_store = await _active_profile_store(tmp_path, profile_name)
blob_storage = FilesystemBlobStorage(tmp_path / "blobs")
embedding_client = httpx.AsyncClient(transport=_embedding_transport(), follow_redirects=False)
repository = MemoryDocumentRepository()
registry = MemorySourceRegistry()
handler = _build_handler(
tmp_path,
profile_store=profile_store,
repository=repository,
registry=registry,
embedding_client=embedding_client,
qdrant_client=qdrant_client,
)
collection_name = f"rag_dense_{profile_name}_v1"
try:
# 1. Ingest: source A owns two documents, source B one, and one ad
# hoc upload names no source at all. The identities below are the
# *inputs*; nothing is read back from Qdrant to build the filters.
await _ingest(
handler,
blob_storage,
content=ENGLISH_DOCUMENT.encode(),
name="runbook.txt",
source_id=SOURCE_A,
external_id="runbooks/gateway",
)
await _ingest(
handler,
blob_storage,
content=DUTCH_DOCUMENT.encode(),
name="firewall.txt",
source_id=SOURCE_A,
external_id="handleidingen/firewall",
)
await _ingest(
handler,
blob_storage,
content=SOURCE_B_DOCUMENT.encode(),
name="certificates.txt",
source_id=SOURCE_B,
external_id="procedures/certificates",
)
await _ingest(handler, blob_storage, content=AD_HOC_DOCUMENT.encode(), name="menu.txt")
assert len(repository.published) == 4
documents_by_source: dict[UUID, set[UUID]] = {}
for document in repository.documents:
documents_by_source.setdefault(document.source_id, set()).add(document.id)
assert len(documents_by_source[SOURCE_A]) == 2
assert len(documents_by_source[SOURCE_B]) == 1
uploads_source = upload_source_id(SPACE_ID)
assert len(documents_by_source[uploads_source]) == 1
assert set(registry.records) == {SOURCE_A, SOURCE_B, uploads_source}
# 2 + 3. Every stored point has both a dense and a sparse vector, and
# stores exactly the caller's source identity.
records, _ = await qdrant_client.scroll(
collection_name=collection_name,
limit=100,
with_payload=True,
with_vectors=True,
)
assert records
stored_sources: set[str] = set()
for record in records:
vectors = record.vector
assert isinstance(vectors, dict)
assert len(vectors["dense"]) == DIMENSIONS
sparse = vectors["sparse"]
indices = getattr(sparse, "indices", None) or sparse["indices"]
assert len(indices) > 0
assert record.payload is not None
stored_sources.add(str(record.payload["source_id"]))
assert stored_sources == {str(SOURCE_A), str(SOURCE_B), str(uploads_source)}
pipeline = RetrievalPipeline(
qdrant_client=qdrant_client,
embedding_profile_store=profile_store,
embedding_adapter_factory=create_embedding_adapter_factory(
settings=Settings(env="test", ollama_url="http://ollama:11434"),
http_client=embedding_client,
),
)
executor = ProductionSearchExecutor(pipeline, _TitleLookup()) # type: ignore[arg-type]
# 5 + 6. Sparse retrieval: the identifier token only the English
# runbook contains must surface it, fused from both backends without
# degradation.
sparse_led = await executor.execute(_search_request("cycle breaker BRK-4417"))
assert sparse_led.degradation_codes == ()
assert sparse_led.hits
top = sparse_led.hits[0]
assert "BRK-4417" in top.text
assert top.fused.sparse_rank is not None
assert top.sparse_score is not None
# Provenance reports the caller's source identity, not a synthetic one.
assert top.fused.candidate.provenance.source_id == SOURCE_A
assert top.citation.source_uri == f"ragcore://source/{SOURCE_A}"
# 4. Dense retrieval: querying with the exact stored chunk text embeds
# to the identical vector, so it must be the dense top hit.
dense_led = await executor.execute(_search_request(top.text))
assert dense_led.hits
assert dense_led.hits[0].fused.dense_rank == 1
assert dense_led.hits[0].dense_score == pytest.approx(1.0, abs=1e-5)
assert dense_led.hits[0].citation.chunk_id == top.citation.chunk_id
# 7a. Language filter: detected Dutch content is retrievable as nl,
# and the nl filter excludes the English runbook.
dutch_only = await executor.execute(
_search_request(
"firewall segmentatie productienetwerk",
filters=SearchFilterData(languages=("nl",)),
)
)
assert dutch_only.hits
assert all("BRK-4417" not in hit.text for hit in dutch_only.hits)
# 7b. document_ids filter constrains to exactly one document.
target_document = top.citation.document_id
only_document = await executor.execute(
_search_request(
"runbook firewall breaker segmentatie certificate",
filters=SearchFilterData(document_ids=(target_document,)),
)
)
assert only_document.hits
assert _doc_ids(only_document) == {target_document}
# 8. Source filter on the ORIGINAL input identities.
broad_query = "runbook firewall breaker segmentatie certificate rotation soup menu"
from_a = await executor.execute(
_search_request(broad_query, filters=SearchFilterData(source_ids=(SOURCE_A,)))
)
assert from_a.hits
assert _doc_ids(from_a) == documents_by_source[SOURCE_A]
assert {hit.fused.candidate.provenance.source_id for hit in from_a.hits} == {SOURCE_A}
from_b = await executor.execute(
_search_request(broad_query, filters=SearchFilterData(source_ids=(SOURCE_B,)))
)
assert from_b.hits
assert _doc_ids(from_b) == documents_by_source[SOURCE_B]
# That source B's content is really reachable is proven the way the
# identifier assertions above prove it: with the one token that only
# the wanted chunk contains.
#
# It has to be *only* that token. This document chunks into a heading
# ("Certificate Rotation Procedure") and a body, both under the same
# parent, and near-duplicate suppression in `fuse_candidates` returns
# exactly one per parent. A query that also says "certificate" or
# "rotation" hands the heading -- three words, every one of them a
# match -- the better sparse score, and the body loses its own
# identifier. The heading carries no "CERT-8820", so this query can
# only be answered by the chunk under test.
certificates = await executor.execute(
_search_request("CERT-8820", filters=SearchFilterData(source_ids=(SOURCE_B,)))
)
assert certificates.hits
assert any("CERT-8820" in hit.text for hit in certificates.hits)
from_uploads = await executor.execute(
_search_request(broad_query, filters=SearchFilterData(source_ids=(uploads_source,)))
)
assert from_uploads.hits
assert _doc_ids(from_uploads) == documents_by_source[uploads_source]
# Source + document filters compose; a document of another source is
# excluded even when named explicitly.
[doc_b] = documents_by_source[SOURCE_B]
crossed = await executor.execute(
_search_request(
broad_query,
filters=SearchFilterData(source_ids=(SOURCE_A,), document_ids=(doc_b,)),
)
)
assert crossed.hits == ()
# A filter for a foreign source excludes everything.
nothing = await executor.execute(
_search_request(broad_query, filters=SearchFilterData(source_ids=(uuid4(),)))
)
assert nothing.hits == ()
finally:
await qdrant_client.delete_collection(collection_name)
await embedding_client.aclose()
def _minimal_pdf() -> bytes:
"""A one-page PDF with a real text layer, built without external tools."""
content_stream = (
b"BT /F1 18 Tf 72 720 Td (Backup Restore Procedure) Tj"
b" 0 -30 Td (Verify snapshot SNAP-9911 before restoring the archive.) Tj ET"
)
objects = [
b"<< /Type /Catalog /Pages 2 0 R >>",
b"<< /Type /Pages /Kids [3 0 R] /Count 1 >>",
b"<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] /Contents 4 0 R"
b" /Resources << /Font << /F1 5 0 R >> >> >>",
b"<< /Length %d >>\nstream\n" % len(content_stream) + content_stream + b"\nendstream",
b"<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>",
]
out = b"%PDF-1.4\n"
offsets = []
for index, body in enumerate(objects, 1):
offsets.append(len(out))
out += b"%d 0 obj\n" % index + body + b"\nendobj\n"
xref = len(out)
out += b"xref\n0 %d\n0000000000 65535 f \n" % (len(objects) + 1)
for offset in offsets:
out += b"%010d 00000 n \n" % offset
out += b"trailer\n<< /Size %d /Root 1 0 R >>\nstartxref\n%d\n%%%%EOF\n" % (
len(objects) + 1,
xref,
)
return out
@pytest.mark.integration
@pytest.mark.asyncio
async def test_pdf_upload_to_citation_gate(
tmp_path: Path, qdrant_client: AsyncQdrantClient
) -> None:
"""Gate B: a real PDF flows through the security gate and Docling to
page-located citations."""
pytest.importorskip("docling")
profile_name = f"gate-{uuid4().hex[:10]}"
profile_store = await _active_profile_store(tmp_path, profile_name)
blob_storage = FilesystemBlobStorage(tmp_path / "blobs")
embedding_client = httpx.AsyncClient(transport=_embedding_transport(), follow_redirects=False)
repository = MemoryDocumentRepository()
handler = _build_handler(
tmp_path,
profile_store=profile_store,
repository=repository,
registry=MemorySourceRegistry(),
embedding_client=embedding_client,
qdrant_client=qdrant_client,
)
collection_name = f"rag_dense_{profile_name}_v1"
try:
await _ingest(
handler,
blob_storage,
content=_minimal_pdf(),
name="restore.pdf",
media_type="application/pdf",
)
assert len(repository.published) == 1
[version] = repository.versions.values()
assert version.parser_version.startswith("docling-")
pipeline = RetrievalPipeline(
qdrant_client=qdrant_client,
embedding_profile_store=profile_store,
embedding_adapter_factory=create_embedding_adapter_factory(
settings=Settings(env="test", ollama_url="http://ollama:11434"),
http_client=embedding_client,
),
)
executor = ProductionSearchExecutor(pipeline, _TitleLookup()) # type: ignore[arg-type]
result = await executor.execute(_search_request("snapshot SNAP-9911 restore archive"))
assert result.degradation_codes == ()
assert result.hits
top = result.hits[0]
assert "SNAP-9911" in top.text
# The citation carries the page the chunk came from.
assert top.citation.location.get("page_start") == 1
assert top.citation.location.get("page_end") == 1
finally:
await qdrant_client.delete_collection(collection_name)
await embedding_client.aclose()