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,248 @@
"""System gate: the operations console against real PostgreSQL, two tenants.
Everything below the HTTP client is the deployed thing: the real FastAPI
application (`create_app`, its middleware stack and routes), the real
`PostgresJobRepository`, the real `PostgresSourceRegistry` for job source-name
enrichment, and a real PostgreSQL database. The one substitution is
authentication -- `get_principal` is overridden so the test can be two
different operators without standing up OIDC -- which is exactly the boundary
under test: given a correctly authenticated principal, what may they see and
change?
The composed proof, in one run:
* workspace A's admin lists jobs and sees only A's, with A's counters;
* A's admin opening B's job is denied, and the response contains none of B's
metadata (filename, source id, source name, failure code);
* A's admin retrying B's job is denied, and B's row is afterwards unchanged in
the database -- status, attempt count, availability and lease all identical;
* B's own admin can still retry B's job, so the refusal above was scoping and
not a broken feature;
* a legacy job with no owner is invisible and immutable for both of them;
* the platform administrator sees all three and may act on them.
"""
from __future__ import annotations
import asyncio
import sys
from datetime import UTC, datetime
from uuid import UUID, uuid4
import pytest
import pytest_asyncio
from fastapi.testclient import TestClient
from sqlalchemy import text
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker
from ragcore.api.v1.control.dependencies import get_principal
from ragcore.application.jobs import Job, JobStatus
from ragcore.config import Settings
from ragcore.domain.authorization import Principal, PrincipalType
from ragcore.domain.control import Role
from ragcore.infrastructure.database import create_database_engine, create_session_factory
from ragcore.infrastructure.database.jobs import PostgresJobRepository
from ragcore.main import create_app
pytestmark = [pytest.mark.integration, pytest.mark.security]
WORKSPACE_A = uuid4()
WORKSPACE_B = uuid4()
SPACE_A = uuid4()
SPACE_B = uuid4()
SOURCE_A = uuid4()
SOURCE_B = uuid4()
@pytest.fixture(scope="session")
def event_loop_policy() -> asyncio.AbstractEventLoopPolicy:
if sys.platform == "win32":
return asyncio.WindowsSelectorEventLoopPolicy()
return asyncio.get_event_loop_policy()
async def _empty_the_queue(factory: async_sessionmaker[AsyncSession]) -> None:
"""Leave ``jobs.jobs`` as this test found it --- before *and* after.
The queue is one shared table, and the ingestion and system gates later
in a full run drive a real ``JobWorker``, which claims the oldest
*available* job in it. A leftover fixture job is therefore claimed instead
of theirs, and they fail for a reason that has nothing to do with what
they test.
"""
async with factory() as session:
await session.execute(text("TRUNCATE jobs.events, jobs.attempts, jobs.jobs CASCADE"))
await session.commit()
@pytest_asyncio.fixture
async def factory() -> async_sessionmaker[AsyncSession]:
engine = create_database_engine(Settings(env="test"))
session_factory = create_session_factory(engine)
await _empty_the_queue(session_factory)
try:
yield session_factory
finally:
await _empty_the_queue(session_factory)
await engine.dispose()
def _job(workspace_id: UUID, space_id: UUID, source_id: UUID, name: str, key: str) -> Job:
return Job(
id=uuid4(),
workspace_id=workspace_id,
job_key=key,
operation="ingest_upload",
target_key=f"blob:{key}",
profile_version="upload-v1",
payload={
"original_name": name,
"source_id": str(source_id),
"space_id": str(space_id),
"workspace_id": str(workspace_id),
"media_type": "text/markdown",
},
available_at=datetime.now(UTC),
trace_id=f"trace-{key}",
)
async def _make_terminal(factory: async_sessionmaker[AsyncSession], job_id: UUID) -> None:
"""Put a job in the one state the Control Plane offers Retry for."""
async with factory() as session, session.begin():
await session.execute(
text(
"UPDATE jobs.jobs SET status = 'terminal', attempt_count = 3, "
"last_error_code = 'retryable_error', "
"last_error_details = CAST(:details AS json) WHERE id = :id"
),
{
"id": job_id,
"details": f'{{"failure_code": "embedding_failed_{job_id.hex[:8]}"}}',
},
)
async def _plant_legacy(factory: async_sessionmaker[AsyncSession]) -> UUID:
job_id = uuid4()
now = datetime.now(UTC)
async with factory() as session, session.begin():
await session.execute(
text(
"""
INSERT INTO jobs.jobs (id, job_key, operation, target_key, profile_version,
payload, status, available_at, max_attempts,
attempt_count, trace_id, last_error_code,
last_error_details, created_at, updated_at)
VALUES (:id, 'legacy-gate', 'ingest_upload', 'blob:legacy', 'upload-v1',
CAST(:payload AS json), 'terminal', :now, 3, 3, 'trace-legacy',
'retryable_error', CAST('{}' AS json), :now, :now)
"""
),
{
"id": job_id,
"payload": f'{{"original_name": "legacy-unowned.md", '
f'"workspace_id": "{WORKSPACE_A}"}}',
"now": now,
},
)
return job_id
async def _row(factory: async_sessionmaker[AsyncSession], job_id: UUID) -> tuple[object, ...]:
async with factory() as session:
row = (
await session.execute(
text(
"SELECT status, attempt_count, available_at, updated_at, leased_by, "
"lease_until FROM jobs.jobs WHERE id = :id"
),
{"id": job_id},
)
).one()
return tuple(row)
def _client(
factory: async_sessionmaker[AsyncSession], workspace_id: UUID, role: Role
) -> TestClient:
app = create_app()
principal = Principal(uuid4(), workspace_id, PrincipalType.HUMAN, frozenset({role}))
app.dependency_overrides[get_principal] = lambda: principal
app.state.job_repository = PostgresJobRepository(factory)
app.state.session_factory = factory
return TestClient(app)
async def test_cross_workspace_operations_gate(
factory: async_sessionmaker[AsyncSession],
) -> None:
repository = PostgresJobRepository(factory)
a1 = await repository.enqueue(_job(WORKSPACE_A, SPACE_A, SOURCE_A, "alpha-one.md", "a1"))
a2 = await repository.enqueue(_job(WORKSPACE_A, SPACE_A, SOURCE_A, "alpha-two.md", "a2"))
b1 = await repository.enqueue(_job(WORKSPACE_B, SPACE_B, SOURCE_B, "bravo-secret.md", "b1"))
await _make_terminal(factory, a1.id)
await _make_terminal(factory, b1.id)
legacy_id = await _plant_legacy(factory)
admin_a = _client(factory, WORKSPACE_A, Role.WORKSPACE_ADMIN)
admin_b = _client(factory, WORKSPACE_B, Role.WORKSPACE_ADMIN)
auditor_a = _client(factory, WORKSPACE_A, Role.AUDITOR)
platform = _client(factory, WORKSPACE_A, Role.PLATFORM_ADMIN)
# -- listing -----------------------------------------------------------
listing = admin_a.get("/admin/jobs")
assert listing.status_code == 200
assert "alpha-one.md" in listing.text and "alpha-two.md" in listing.text
for secret in ("bravo-secret.md", str(b1.id), str(SOURCE_B), "legacy-unowned.md"):
assert secret not in listing.text
assert "This workspace" in listing.text
# -- detail ------------------------------------------------------------
assert admin_a.get(f"/admin/jobs/{a1.id}").status_code == 200
denied = admin_a.get(f"/admin/jobs/{b1.id}")
assert denied.status_code == 404
for secret in ("bravo-secret.md", str(SOURCE_B), "embedding_failed"):
assert secret not in denied.text
# -- mutation ----------------------------------------------------------
before = await _row(factory, b1.id)
refused = admin_a.post(f"/admin/jobs/{b1.id}/retry", follow_redirects=False)
assert refused.status_code == 404
assert await _row(factory, b1.id) == before, "a refused retry must not touch the job"
# ...and B's own admin can still recover it.
allowed = admin_b.post(f"/admin/jobs/{b1.id}/retry", follow_redirects=False)
assert allowed.status_code == 303
async with factory() as session:
status = (
await session.execute(
text("SELECT status FROM jobs.jobs WHERE id = :id"), {"id": b1.id}
)
).scalar_one()
assert status == JobStatus.READY.value
# -- auditor is read-only ---------------------------------------------
assert auditor_a.get("/admin/jobs").status_code == 200
assert auditor_a.post(f"/admin/jobs/{a1.id}/retry").status_code == 403
# -- legacy job fails closed for both tenants -------------------------
legacy_before = await _row(factory, legacy_id)
for client in (admin_a, admin_b):
assert client.get(f"/admin/jobs/{legacy_id}").status_code == 404
retry = client.post(f"/admin/jobs/{legacy_id}/retry", follow_redirects=False)
assert retry.status_code == 404
assert await _row(factory, legacy_id) == legacy_before
# -- platform admin keeps global operations ---------------------------
global_listing = platform.get("/admin/jobs")
assert global_listing.status_code == 200
for expected in ("alpha-one.md", "bravo-secret.md", "legacy-unowned.md", "All workspaces"):
assert expected in global_listing.text
assert platform.get(f"/admin/jobs/{legacy_id}").status_code == 200
assert (
platform.post(f"/admin/jobs/{legacy_id}/retry", follow_redirects=False).status_code == 303
)
assert a2.workspace_id == WORKSPACE_A
@@ -0,0 +1,502 @@
"""System gate: HTTP upload -> PostgreSQL job -> worker lease -> production
ingest handler -> security gate -> parser -> chunker -> dense + sparse ->
Qdrant -> published -> HTTP search, with the caller's source identity intact.
Everything below the HTTP client is real: the real FastAPI application
(``create_app``, with its middleware stack, bearer authentication, rate
limiting and routes), real PostgreSQL (control plane, credentials, jobs,
content, source registry, audit), the real ``PostgresJobRepository`` lease
protocol driven by the real ``JobWorker``, the real
``build_ingest_upload_handler`` production wiring, and a real Qdrant server.
HTTP is exercised end to end through the ASGI interface of the real app (an
in-process transport, not a TCP socket -- the only ingredient that is not the
deployed process boundary itself). The one deliberate substitution is the
embedding *model*: a deterministic hash-based HTTP transport stands in for
Ollama, for both the worker's document embedding and the app's query
embedding, because the gate proves orchestration and correctness, not
embedding quality.
Proven, in order:
* the upload is accepted over HTTP with a bearer token (202 + job url);
* the job really exists in PostgreSQL and is READY;
* the worker leases and executes it; the job ends SUCCEEDED with the attempt
recorded against this worker;
* the document is PUBLISHED (searchable) with its canonical source id and
the job id on its version;
* every stored Qdrant point has a dense *and* a sparse vector and carries the
caller's source id;
* ``POST /v1/search`` over HTTP finds the document; provenance/citation name
the same source; ``source_ids=[A]`` returns it and a foreign source does not;
* ``GET /v1/jobs/{id}`` reports ``succeeded``;
* failure path: content the security gate quarantines ends with the job
terminal AND the document version FAILED with a content-free failure code
-- never "processing" forever.
"""
from __future__ import annotations
import asyncio
import contextlib
import hashlib
import json
import math
import os
import sys
from collections.abc import AsyncIterator
from datetime import UTC, datetime, timedelta
from pathlib import Path
from uuid import UUID, uuid4
import httpx
import pytest
from qdrant_client import AsyncQdrantClient
from sqlalchemy import text
from sqlalchemy.ext.asyncio import AsyncEngine
from ragcore.application.audit import AuditEventService
from ragcore.application.documents import DocumentFailureCode, DocumentVersionState
from ragcore.application.identity.credentials import ServiceAccountCredentialService
from ragcore.application.jobs import JobStatus, JobWorker
from ragcore.config import Settings, get_settings
from ragcore.domain.control import DataPolicy, Role, Scope, ServiceAccount, Workspace
from ragcore.domain.models import EmbeddingProfile, EmbeddingProvider
from ragcore.infrastructure.database import (
create_database_engine,
create_session_factory,
session_scope,
)
from ragcore.infrastructure.database.audit import SessionScopedAuditEventRepository
from ragcore.infrastructure.database.content import PostgresDocumentRepository
from ragcore.infrastructure.database.control import ControlPlaneRepository
from ragcore.infrastructure.database.identity import PostgresCredentialRepository
from ragcore.infrastructure.database.jobs import PostgresJobRepository
from ragcore.infrastructure.database.sources import PostgresSourceRegistry
from ragcore.infrastructure.ingestion import build_ingest_upload_handler
from ragcore.infrastructure.models.persistence import JsonEmbeddingProfileStore
from ragcore.infrastructure.qdrant.rebuild import QdrantRebuildStore
pytestmark = [pytest.mark.integration]
DIMENSIONS = 8
DOCUMENT_ONE = (
"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"
)
DOCUMENT_TWO = (
"Certificate Rotation Procedure\n\n"
"Rotate the edge certificate CERT-8820 every ninety days using the vault job.\n"
)
@pytest.fixture(scope="session")
def event_loop_policy() -> asyncio.AbstractEventLoopPolicy:
if sys.platform == "win32":
return asyncio.WindowsSelectorEventLoopPolicy()
return asyncio.get_event_loop_policy()
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)
return httpx.Response(
200, json={"embeddings": [_deterministic_vector(text) for text in body["input"]]}
)
return httpx.MockTransport(handler)
@pytest.fixture
async def db_engine() -> AsyncIterator[AsyncEngine]:
engine = create_database_engine(Settings(env="test"))
try:
yield engine
finally:
await engine.dispose()
@pytest.fixture
async def uninitialized_embedding_selection(db_engine: AsyncEngine) -> AsyncIterator[None]:
"""Exercise first-start selection bootstrap, then restore shared test state."""
async with db_engine.begin() as connection:
result = await connection.execute(
text(
"SELECT generation, initialized, active_selection, previous_selection, updated_at "
"FROM profiles.embedding_selection_state WHERE singleton_id=1"
)
)
row = result.mappings().one_or_none()
if row is None:
pytest.fail("migration sentinel missing; apply Alembic head before system tests")
previous_state = dict(row)
reset = await connection.execute(
text(
"UPDATE profiles.embedding_selection_state "
"SET generation=0, initialized=false, active_selection=NULL, "
"previous_selection=NULL, updated_at=now() WHERE singleton_id=1"
)
)
if reset.rowcount != 1:
pytest.fail("migration sentinel missing; apply Alembic head before system tests")
try:
yield
finally:
async with db_engine.begin() as connection:
restored = await connection.execute(
text(
"UPDATE profiles.embedding_selection_state SET generation=:generation, "
"initialized=:initialized, "
"active_selection=CAST(:active_selection AS json), "
"previous_selection=CAST(:previous_selection AS json), "
"updated_at=:updated_at WHERE singleton_id=1"
),
{
"generation": previous_state["generation"],
"initialized": previous_state["initialized"],
"active_selection": (
json.dumps(previous_state["active_selection"])
if previous_state["active_selection"] is not None
else None
),
"previous_selection": (
json.dumps(previous_state["previous_selection"])
if previous_state["previous_selection"] is not None
else None
),
"updated_at": previous_state["updated_at"],
},
)
if restored.rowcount != 1:
pytest.fail("embedding selection state row disappeared during system test")
@pytest.fixture
async def qdrant() -> 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 _activate_profile(settings: Settings, name: str) -> EmbeddingProfile:
store = JsonEmbeddingProfileStore(settings.embedding_profiles_path)
profile = EmbeddingProfile(
id=uuid4(),
profile=name,
version="v1",
provider=EmbeddingProvider.OLLAMA,
model_identifier="system-gate:v1",
model_digest="c" * 64,
dimensions=DIMENSIONS,
context_limit=2048,
tokenizer="gate",
data_policy=DataPolicy.LOCAL_ONLY,
)
await store.add(profile)
await store.activate(profile.id)
return profile
async def _provision_caller(db_engine: AsyncEngine, settings: Settings) -> tuple[UUID, UUID, str]:
"""Real workspace, space, application, service account, grant and bearer token."""
session_factory = create_session_factory(db_engine)
workspace_id = uuid4()
workspace = Workspace(
workspace_id, f"System gate {workspace_id.hex[:6]}", f"sg-{workspace_id.hex[:8]}"
)
application = workspace.create_application(
id=uuid4(),
name="System gate client",
slug=f"sg-client-{uuid4().hex[:8]}",
scopes=frozenset({Scope.SEARCH, Scope.CONTEXT, Scope.SOURCES_SYNC, Scope.DOCUMENTS_READ}),
)
space = workspace.create_knowledge_space(
id=uuid4(), name="System gate space", slug=f"sg-space-{uuid4().hex[:8]}"
)
service_account = ServiceAccount(
id=uuid4(),
workspace_id=workspace_id,
application_id=application.id,
name="System gate service account",
scopes=application.scopes,
)
async with session_scope(session_factory) as session:
repository = ControlPlaneRepository(session)
await repository.create_workspace(workspace)
await repository.create_space(space)
await repository.create_application(application)
await repository.create_service_account(service_account)
await repository.create_grant(
workspace.grant(
id=uuid4(), application=application, knowledge_space=space, role=Role.EDITOR
)
)
await session.commit()
async def load_service_account(account_id: UUID) -> ServiceAccount | None:
async with session_scope(session_factory) as session:
return await ControlPlaneRepository(session).get_service_account_by_id(account_id)
async with session_scope(session_factory) as session:
credentials = ServiceAccountCredentialService(
PostgresCredentialRepository(session),
settings.api_token_pepper.get_secret_value(),
service_account_loader=load_service_account,
)
issued = await credentials.issue(service_account, scopes=service_account.scopes)
await session.commit()
return workspace_id, space.id, issued.token
async def _upload(
client: httpx.AsyncClient,
*,
token: str,
space_id: UUID,
source_id: UUID | None,
external_id: str | None,
filename: str,
content: bytes,
) -> UUID:
data: dict[str, str] = {"space_id": str(space_id)}
if source_id is not None:
data["source_id"] = str(source_id)
if external_id is not None:
data["external_id"] = external_id
response = await client.post(
"/v1/uploads",
data=data,
files={"file": (filename, content, "text/plain")},
headers={"Authorization": f"Bearer {token}", "Idempotency-Key": uuid4().hex},
)
assert response.status_code == 202, response.text
body = response.json()
assert body["status_url"] == f"/v1/jobs/{body['job_id']}"
return UUID(body["job_id"])
async def test_http_upload_to_worker_to_index_to_http_search(
tmp_path: Path,
db_engine: AsyncEngine,
qdrant: AsyncQdrantClient,
uninitialized_embedding_selection: None,
) -> None:
settings = get_settings()
profile_name = f"sysgate-{uuid4().hex[:10]}"
await _activate_profile(settings, profile_name)
collection_name = f"rag_dense_{profile_name}_v1"
alias_name = f"rag_dense_{profile_name}_active"
workspace_id, space_id, token = await _provision_caller(db_engine, settings)
session_factory = create_session_factory(db_engine)
source_a = uuid4()
from ragcore.main import create_app
model_client = httpx.AsyncClient(transport=_embedding_transport(), follow_redirects=False)
app = create_app(model_http_client=model_client)
jobs = PostgresJobRepository(session_factory)
documents = PostgresDocumentRepository(session_factory)
worker = JobWorker(
jobs,
{
"ingest_upload": build_ingest_upload_handler(
settings=settings,
document_repository=documents,
source_registry=PostgresSourceRegistry(session_factory),
security_audit=AuditEventService(
SessionScopedAuditEventRepository(session_factory)
),
embedding_http_client=model_client,
qdrant_store=QdrantRebuildStore(qdrant),
)
},
worker_id="system-gate-worker",
lease_duration=timedelta(seconds=60),
poll_interval=0.05,
)
lifespan_started = False
try:
async with contextlib.AsyncExitStack() as stack:
await stack.enter_async_context(app.router.lifespan_context(app))
lifespan_started = True
http = await stack.enter_async_context(
httpx.AsyncClient(
transport=httpx.ASGITransport(app=app), base_url="http://ragcore.test"
)
)
# --- 1. HTTP upload accepted ------------------------------------
job_one = await _upload(
http,
token=token,
space_id=space_id,
source_id=source_a,
external_id="runbooks/gateway",
filename="gateway.txt",
content=DOCUMENT_ONE.encode(),
)
job_two = await _upload(
http,
token=token,
space_id=space_id,
source_id=source_a,
external_id="procedures/certificates",
filename="certificates.txt",
content=DOCUMENT_TWO.encode(),
)
# --- 2. real job persisted ---------------------------------------
persisted = await jobs.get(job_one)
assert persisted is not None
assert persisted.status is JobStatus.READY
assert persisted.payload["source_id"] == str(source_a)
status_before = await http.get(
f"/v1/jobs/{job_one}", headers={"Authorization": f"Bearer {token}"}
)
assert status_before.status_code == 200
assert status_before.json()["status"] == "ready"
# --- 3. the worker leases and executes both jobs -----------------
assert await worker.run_once() is True
assert await worker.run_once() is True
for job_id in (job_one, job_two):
done = await jobs.get(job_id)
assert done is not None
assert done.status is JobStatus.SUCCEEDED, done.last_error_code
assert done.attempt_count == 1
status_after = await http.get(
f"/v1/jobs/{job_id}", headers={"Authorization": f"Bearer {token}"}
)
assert status_after.json()["status"] == "succeeded"
# --- 4. documents are published with canonical source identity ---
owned = await documents.list_documents_by_source(workspace_id, source_a)
assert {document.external_id for document in owned} == {
"runbooks/gateway",
"procedures/certificates",
}
assert all(document.source_id == source_a for document in owned)
for document in owned:
[version] = await documents.list_searchable_versions(document.id)
assert version.state is DocumentVersionState.PUBLISHED
assert version.job_id in {job_one, job_two}
# --- 5. Qdrant points: dense + sparse, caller's source id --------
assert (await qdrant.get_collection(alias_name)).status is not None
records, _ = await qdrant.scroll(
collection_name=collection_name, limit=100, with_payload=True, with_vectors=True
)
assert records
for record in records:
vectors = record.vector
assert isinstance(vectors, dict)
assert len(vectors["dense"]) == DIMENSIONS
sparse = vectors["sparse"]
assert len(getattr(sparse, "indices", None) or sparse["indices"]) > 0
assert record.payload is not None
assert record.payload["source_id"] == str(source_a)
assert record.payload["status"] == "published"
# --- 6. HTTP search finds it; source filter honors input identity
search = await http.post(
"/v1/search",
json={
"query": "cycle breaker BRK-4417",
"requested_space_ids": [str(space_id)],
"max_results": 5,
},
headers={"Authorization": f"Bearer {token}"},
)
assert search.status_code == 200, search.text
body = search.json()
assert body["effective_space_ids"] == [str(space_id)]
assert body["degraded"] is False, body["degradation_codes"]
assert body["results"], body
top = body["results"][0]
assert "BRK-4417" in top["text"]
assert top["scores"]["sparse"] is not None
assert top["scores"]["dense"] is not None
assert top["citation"]["source_uri"] == f"ragcore://source/{source_a}"
assert top["citation"]["title"] == "gateway.txt"
assert body["timings_ms"]["dense_search"] >= 0
filtered = await http.post(
"/v1/search",
json={
"query": "runbook breaker certificate rotation",
"requested_space_ids": [str(space_id)],
"max_results": 10,
"filters": {"source_ids": [str(source_a)]},
},
headers={"Authorization": f"Bearer {token}"},
)
assert filtered.status_code == 200, filtered.text
returned = {result["citation"]["document_id"] for result in filtered.json()["results"]}
assert returned == {str(document.id) for document in owned}
foreign = await http.post(
"/v1/search",
json={
"query": "runbook breaker certificate rotation",
"requested_space_ids": [str(space_id)],
"filters": {"source_ids": [str(uuid4())]},
},
headers={"Authorization": f"Bearer {token}"},
)
assert foreign.status_code == 200
assert foreign.json()["results"] == []
# --- 7. failure path: terminal job <-> FAILED document ------------
poisoned = await _upload(
http,
token=token,
space_id=space_id,
source_id=source_a,
external_id="poison/pdf-as-text",
filename="innocent.txt",
content=b"%PDF-1.7 this is really a PDF pretending to be text",
)
assert await worker.run_once() is True
failed_job = await jobs.get(poisoned)
assert failed_job is not None
assert failed_job.status is JobStatus.TERMINAL
assert failed_job.last_error_details.get("failure_code") == (
DocumentFailureCode.SECURITY_QUARANTINED.value
)
failed_status = await http.get(
f"/v1/jobs/{poisoned}", headers={"Authorization": f"Bearer {token}"}
)
assert failed_status.json()["status"] == "terminal"
poisoned_document = await documents.get_document_by_source(
workspace_id, str(source_a), "poison/pdf-as-text"
)
assert poisoned_document is not None
[failed_version] = await documents.list_versions(poisoned_document.id)
assert failed_version.state is DocumentVersionState.FAILED
assert failed_version.failure_code == DocumentFailureCode.SECURITY_QUARANTINED.value
assert failed_version.failure_retryable is False
assert failed_version.job_id == poisoned
assert failed_version.failed_at is not None
assert await documents.list_searchable_versions(poisoned_document.id) == ()
assert datetime.now(UTC) - failed_version.failed_at < timedelta(minutes=5)
finally:
if not lifespan_started:
app.state.telemetry.shutdown()
await app.state.database_engine.dispose()
await app.state.qdrant_client.close()
await model_client.aclose()
with contextlib.suppress(Exception):
await qdrant.delete_collection(collection_name)