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,353 @@
from __future__ import annotations
import json
import tempfile
from pathlib import Path
from types import SimpleNamespace
from uuid import uuid4
import httpx
import pytest
from qdrant_client.http import models
from ragcore.application.models.selection import ResolvedEmbeddingSelection
from ragcore.application.retrieval.authorization import AuthorizedRetrievalScope
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, EmbeddingSelection
from ragcore.infrastructure.models.embedding_factory import create_embedding_adapter_factory
from ragcore.infrastructure.models.persistence import JsonEmbeddingProfileStore
from ragcore.web.query_lab.executor import PipelineQueryLabExecutor, QueryLabExecutionRequest
from ragcore.web.query_lab.models import QueryLabQuery
WORKSPACE_ID = uuid4()
SPACE_ID = uuid4()
DOCUMENT_ID = uuid4()
DOCUMENT_VERSION_ID = uuid4()
CHUNK_ID = uuid4()
SOURCE_ID = uuid4()
EMBEDDING_PROFILE = EmbeddingProfile(
id=uuid4(),
profile="hybrid",
version="v1",
provider=EmbeddingProvider.OLLAMA,
model_identifier="nomic-embed-text:v1",
model_digest="0" * 64,
dimensions=2,
context_limit=2048,
tokenizer="cl100k",
data_policy=DataPolicy.LOCAL_ONLY,
)
_PAYLOAD = {
"workspace_id": str(WORKSPACE_ID),
"space_id": str(SPACE_ID),
"document_id": str(DOCUMENT_ID),
"document_version_id": str(DOCUMENT_VERSION_ID),
"chunk_id": str(CHUNK_ID),
"source_id": str(SOURCE_ID),
"parent_chunk_id": None,
"status": "published",
"content_type": "text/plain",
"language": "en",
"trust_level": "source",
"embedding_model_digest": EMBEDDING_PROFILE.model_digest,
"chunk_profile_version": EMBEDDING_PROFILE.version,
"text": "The runbook says to restart the payment service after a failover.",
"location": {"page": 1},
}
class FakeQdrantClient:
def __init__(
self,
*,
alias_target: str = "rag_dense_hybrid_v1",
payload: dict[str, object] = _PAYLOAD,
) -> None:
self.alias_target = alias_target
self.payload = payload
self.retrieve_calls: list[dict[str, object]] = []
self.query_collections: list[str] = []
async def get_aliases(self, **kwargs: object) -> SimpleNamespace:
return SimpleNamespace(
aliases=[
SimpleNamespace(
alias_name="rag_dense_hybrid_active", collection_name=self.alias_target
)
]
)
async def get_collection(self, collection_name: str, **kwargs: object) -> SimpleNamespace:
return SimpleNamespace(
config=SimpleNamespace(
params=SimpleNamespace(
vectors={"dense": models.VectorParams(size=2, distance=models.Distance.COSINE)},
sparse_vectors={"sparse": models.SparseVectorParams()},
)
)
)
async def query_points(self, **kwargs: object) -> models.QueryResponse:
self.query_collections.append(str(kwargs.get("collection_name")))
return models.QueryResponse(
points=[
models.ScoredPoint(id=str(CHUNK_ID), version=0, score=0.9, payload=self.payload)
]
)
async def retrieve(self, **kwargs: object) -> list[SimpleNamespace]:
self.retrieve_calls.append(kwargs)
ids = kwargs.get("ids", [])
return [SimpleNamespace(id=raw_id, payload=self.payload) for raw_id in ids]
def _embed_transport(vector: tuple[float, ...] = (0.1, 0.2)) -> httpx.MockTransport:
def handler(request: httpx.Request) -> httpx.Response:
return httpx.Response(200, json={"embeddings": [list(vector)]})
return httpx.MockTransport(handler)
def _failing_transport(status_code: int = 503) -> httpx.MockTransport:
def handler(request: httpx.Request) -> httpx.Response:
return httpx.Response(status_code, json={"error": "unavailable"})
return httpx.MockTransport(handler)
def _embedding_adapter_factory(transport: httpx.MockTransport, *, settings: Settings | None = None):
client = httpx.AsyncClient(transport=transport, follow_redirects=False)
return create_embedding_adapter_factory(
settings=settings or Settings(env="test", ollama_url="http://localhost:11434"),
http_client=client,
)
def _empty_store() -> JsonEmbeddingProfileStore:
path = Path(tempfile.mkdtemp()) / "profiles.json"
return JsonEmbeddingProfileStore(path)
async def _activated_store() -> JsonEmbeddingProfileStore:
store = _empty_store()
await store.add(EMBEDDING_PROFILE)
await store.activate(EMBEDDING_PROFILE.id)
return store
async def _activated_store_for(profile: EmbeddingProfile) -> JsonEmbeddingProfileStore:
store = _empty_store()
await store.add(profile)
await store.activate(profile.id)
return store
def _modelforge_profile(*, version: str = "v1") -> EmbeddingProfile:
return EmbeddingProfile(
id=uuid4(),
profile="hybrid",
version=version,
provider=EmbeddingProvider.MODELFORGE,
model_identifier="rag.embedding@1",
model_digest="c" * 64,
dimensions=2,
context_limit=8192,
tokenizer="capability-managed",
data_policy=DataPolicy.LOCAL_ONLY,
embedding_space_id=uuid4(),
)
class FixedSelectionResolver:
def __init__(self, profile: EmbeddingProfile, *, route: str) -> None:
self.selected = ResolvedEmbeddingSelection(
generation=4,
profile=profile,
selection=EmbeddingSelection.from_profile(profile, experiment_route_key=route),
)
async def resolve_active(self) -> ResolvedEmbeddingSelection:
return self.selected
def _request(query: str = "how do I restart the payment service") -> QueryLabExecutionRequest:
return QueryLabExecutionRequest(
principal=Principal(uuid4(), WORKSPACE_ID, PrincipalType.HUMAN, frozenset()),
query=QueryLabQuery(query=query, max_results=5),
scope=AuthorizedRetrievalScope(WORKSPACE_ID, frozenset({SPACE_ID})),
retrieval_run_id=uuid4(),
)
@pytest.mark.asyncio
async def test_degrades_when_no_active_embedding_profile() -> None:
executor = PipelineQueryLabExecutor(
qdrant_client=FakeQdrantClient(),
embedding_profile_store=_empty_store(),
embedding_adapter_factory=_embedding_adapter_factory(_embed_transport()),
)
trace = await executor.execute(_request())
assert trace.degraded is True
assert "no_active_embedding_profile" in trace.degradation_codes
assert trace.dense_candidates == ()
assert trace.revisions.embedding_profile is None
@pytest.mark.asyncio
async def test_degrades_when_embedding_provider_unavailable() -> None:
executor = PipelineQueryLabExecutor(
qdrant_client=FakeQdrantClient(),
embedding_profile_store=await _activated_store(),
embedding_adapter_factory=_embedding_adapter_factory(_failing_transport()),
)
trace = await executor.execute(_request())
assert trace.degraded is True
assert "embedding_provider_unavailable" in trace.degradation_codes
@pytest.mark.asyncio
async def test_full_pipeline_happy_path_produces_a_bounded_trace() -> None:
"""M8-01: no reranking -- the rerank trace section always reports
'skipped', never 'reranked' or 'degraded', since the step no longer runs
(the deployed Ollama never had a working /api/rerank endpoint)."""
executor = PipelineQueryLabExecutor(
qdrant_client=FakeQdrantClient(),
embedding_profile_store=await _activated_store(),
embedding_adapter_factory=_embedding_adapter_factory(_embed_transport()),
)
trace = await executor.execute(_request())
assert trace.degraded is False
assert len(trace.dense_candidates) == 1
assert len(trace.sparse_candidates) == 1
assert len(trace.fusion.candidates) == 1
assert trace.rerank.state == "skipped"
assert trace.rerank.degradation_code is None
assert len(trace.citations) == 1
assert trace.citations[0].document_id == DOCUMENT_ID
assert "restart the payment service" in trace.citations[0].excerpt_preview
assert trace.answerability.status in {"partially_answerable", "not_answerable", "answerable"}
assert trace.revisions.embedding_profile == "hybrid/v1"
assert trace.revisions.reranker_profile is None
assert trace.revisions.collection_alias == "rag_dense_hybrid_active"
assert set(trace.timings_ms) >= {"embed_query", "dense_search", "sparse_search", "rerank"}
@pytest.mark.asyncio
async def test_query_lab_uses_durable_route_and_collection_selection(
tmp_path: Path,
) -> None:
profile = _modelforge_profile()
credential_file = tmp_path / "credential"
credential_file.write_text("mf_" + "x" * 48, encoding="utf-8")
credential_file.chmod(0o600)
seen: list[httpx.Request] = []
def respond(request: httpx.Request) -> httpx.Response:
seen.append(request)
return httpx.Response(
200,
json={
"capability": "rag.embedding@1",
"dimension": profile.dimensions,
"normalized": True,
"embedding_space_id": str(profile.embedding_space_id),
"data": [[1.0, 0.0]],
"request_id": str(uuid4()),
},
)
settings = Settings(
env="test",
ollama_url="http://localhost:11434",
modelforge_base_url="http://modelforge:8000",
modelforge_credential_file=credential_file,
modelforge_embedding_experiment_route="stale-global-route",
)
payload = {
**_PAYLOAD,
"embedding_model_digest": profile.model_digest,
"embedding_space_id": str(profile.embedding_space_id),
}
qdrant = FakeQdrantClient(alias_target="rag_dense_hybrid_old", payload=payload)
executor = PipelineQueryLabExecutor(
qdrant_client=qdrant,
embedding_profile_store=await _activated_store_for(profile),
embedding_adapter_factory=_embedding_adapter_factory(
httpx.MockTransport(respond), settings=settings
),
embedding_selection_resolver=FixedSelectionResolver(profile, route="rc-canary-2026-09"),
)
trace = await executor.execute(_request())
assert trace.degraded is False
assert trace.dense_candidates
assert trace.revisions.embedding_profile == "hybrid/v1"
assert trace.revisions.collection_alias == "rag_dense_hybrid_active"
assert qdrant.query_collections == ["rag_dense_hybrid_v1", "rag_dense_hybrid_v1"]
assert len(seen) == 1
assert seen[0].url.path == "/api/v1/capability-experiments/rc-canary-2026-09/invoke"
assert json.loads(seen[0].content) == {
"input": ["how do I restart the payment service"],
"input_type": "query",
}
@pytest.mark.asyncio
async def test_query_lab_uses_selected_collection_after_alias_changes(
tmp_path: Path,
) -> None:
profile = _modelforge_profile(version="v2")
credential_file = tmp_path / "credential"
credential_file.write_text("mf_" + "x" * 48, encoding="utf-8")
credential_file.chmod(0o600)
seen: list[httpx.Request] = []
def respond(request: httpx.Request) -> httpx.Response:
seen.append(request)
return httpx.Response(
200,
json={
"capability": "rag.embedding@1",
"dimension": profile.dimensions,
"normalized": True,
"embedding_space_id": str(profile.embedding_space_id),
"data": [[1.0, 0.0]],
"request_id": str(uuid4()),
},
)
settings = Settings(
env="test",
ollama_url="http://localhost:11434",
modelforge_base_url="http://modelforge:8000",
modelforge_credential_file=credential_file,
modelforge_embedding_experiment_route="rc-canary-2026-09",
)
payload = {
**_PAYLOAD,
"embedding_model_digest": profile.model_digest,
"embedding_space_id": str(profile.embedding_space_id),
"chunk_profile_version": profile.version,
}
qdrant = FakeQdrantClient(alias_target="rag_dense_hybrid_v1", payload=payload)
executor = PipelineQueryLabExecutor(
qdrant_client=qdrant,
embedding_profile_store=await _activated_store_for(profile),
embedding_adapter_factory=_embedding_adapter_factory(
httpx.MockTransport(respond), settings=settings
),
embedding_selection_resolver=FixedSelectionResolver(profile, route="rc-canary-2026-09"),
)
trace = await executor.execute(_request())
assert trace.degraded is False
assert qdrant.query_collections == ["rag_dense_hybrid_v2", "rag_dense_hybrid_v2"]
assert len(seen) == 1
@@ -0,0 +1,208 @@
from __future__ import annotations
from uuid import uuid4
import pytest
from ragcore.web.query_lab.models import (
AnswerabilityTrace,
CandidateBackendLabel,
CandidateTrace,
ContextTrace,
FusionTrace,
InclusionState,
ParentExpansionTrace,
QueryLabFilterRequest,
QueryLabQuery,
QueryLabTrace,
RerankTrace,
RevisionTrace,
preview,
)
def _candidate(**overrides: object) -> CandidateTrace:
values = {
"rank": 1,
"chunk_id": uuid4(),
"document_id": uuid4(),
"space_id": uuid4(),
"backend": CandidateBackendLabel.DENSE,
"score": 0.5,
"score_label": "similarity_score",
"content_type": "text/plain",
"trust_level": "source",
"language": "en",
"text_preview": "hello",
"text_truncated": False,
"inclusion": InclusionState.INCLUDED,
"reason": "selected_for_fusion",
}
values.update(overrides)
return CandidateTrace(**values)
def test_query_rejects_empty_and_oversized_text() -> None:
with pytest.raises(ValueError):
QueryLabQuery(query=" ")
with pytest.raises(ValueError):
QueryLabQuery(query="x" * 8001)
def test_query_rejects_duplicate_requested_spaces() -> None:
space_id = uuid4()
with pytest.raises(ValueError):
QueryLabQuery(query="test", requested_space_ids=(space_id, space_id))
def test_query_rejects_unsafe_profile() -> None:
with pytest.raises(ValueError):
QueryLabQuery(query="test", profile="not a safe id!")
def test_filter_request_bounds_cardinality() -> None:
with pytest.raises(ValueError):
QueryLabFilterRequest(content_types=tuple(f"type-{i}" for i in range(51)))
def test_preview_truncates_and_flags() -> None:
text, truncated = preview("a" * 500, 400)
assert len(text) == 400
assert truncated is True
text, truncated = preview("short", 400)
assert text == "short"
assert truncated is False
def test_candidate_trace_rejects_unsafe_reason_code() -> None:
with pytest.raises(ValueError):
_candidate(reason="not a safe code!")
def test_candidate_trace_rejects_oversized_preview() -> None:
with pytest.raises(ValueError):
_candidate(text_preview="a" * 401)
def test_fusion_trace_bounds_display_candidates() -> None:
with pytest.raises(ValueError):
FusionTrace(candidates=tuple(_candidate(rank=i) for i in range(1, 22)), suppressed_count=0)
def test_fusion_trace_rejects_negative_suppressed_count() -> None:
with pytest.raises(ValueError):
FusionTrace(candidates=(), suppressed_count=-1)
def test_rerank_trace_rejects_unknown_state() -> None:
with pytest.raises(ValueError):
RerankTrace(state="bogus", degradation_code=None, candidates=(), elapsed_ms=1.0)
def test_answerability_trace_requires_at_least_one_reason() -> None:
with pytest.raises(ValueError):
AnswerabilityTrace(
status="not_answerable",
reasons=(),
evidence_count=0,
document_diversity=0,
source_independence=0,
facet_coverage_ratio=0.0,
thresholds_version="v1",
)
def test_context_trace_requires_positive_max_tokens() -> None:
with pytest.raises(ValueError):
ContextTrace(token_count=0, max_context_tokens=0, block_count=0, citation_ids=())
def test_parent_expansion_trace_rejects_negative_counts() -> None:
with pytest.raises(ValueError):
ParentExpansionTrace(expanded_count=-1, skipped_count=0, total_expansion_tokens=0)
def _full_trace(**overrides: object) -> QueryLabTrace:
from ragcore.web.query_lab.models import EffectiveFilterTrace
values: dict[str, object] = {
"retrieval_run_id": uuid4(),
"query": "example query",
"profile": "hybrid-v1",
"effective_filters": EffectiveFilterTrace(
requested_space_ids=(),
effective_space_ids=(),
denied_space_ids=(),
trust_required_space_ids=(),
requested_content_types=(),
applied_content_types=(),
requested_languages=(),
applied_languages=(),
unsupported_filter_fields=(),
),
"dense_candidates": (),
"sparse_candidates": (),
"fusion": FusionTrace(candidates=(), suppressed_count=0),
"rerank": RerankTrace(
state="skipped", degradation_code=None, candidates=(), elapsed_ms=0.0
),
"parent_expansion": ParentExpansionTrace(0, 0, 0),
"context": ContextTrace(
token_count=0, max_context_tokens=100, block_count=0, citation_ids=()
),
"answerability": AnswerabilityTrace(
status="not_answerable",
reasons=("insufficient_evidence",),
evidence_count=0,
document_diversity=0,
source_independence=0,
facet_coverage_ratio=0.0,
thresholds_version="v1",
),
"citations": (),
"timings_ms": {},
"revisions": RevisionTrace(None, None, "technical/v1", None, "v1"),
"degraded": False,
"degradation_codes": (),
}
values.update(overrides)
return QueryLabTrace(**values)
def test_trace_rejects_unsafe_timing_name() -> None:
with pytest.raises(ValueError):
_full_trace(timings_ms={"not a safe name": 1.0})
def test_trace_rejects_negative_timing() -> None:
with pytest.raises(ValueError):
_full_trace(timings_ms={"embed_query": -1.0})
def test_trace_rejects_duplicate_degradation_codes() -> None:
with pytest.raises(ValueError):
_full_trace(degradation_codes=("backend_timeout", "backend_timeout"))
def test_trace_rejects_too_many_citations() -> None:
from ragcore.web.query_lab.models import CitationTrace
citations = tuple(
CitationTrace(
id=uuid4(),
document_id=uuid4(),
chunk_id=uuid4(),
excerpt_preview="text",
excerpt_truncated=False,
location={},
source_uri="ragcore://source/x",
)
for _ in range(21)
)
with pytest.raises(ValueError):
_full_trace(citations=citations)
def test_trace_accepts_a_well_formed_minimal_result() -> None:
trace = _full_trace()
assert trace.degraded is False
@@ -0,0 +1,203 @@
from __future__ import annotations
from uuid import uuid4
import pytest
from ragcore.api.v1.search.service import AuthorizationInputs
from ragcore.application.authorization import AuthorizationService
from ragcore.application.retrieval.authorization import RetrievalAuthorizationService
from ragcore.domain.authorization import PermissionDenied, Principal, PrincipalType
from ragcore.domain.control import DataPolicy, KnowledgeSpace, Role, UserGrant
from ragcore.web.query_lab.executor import QueryLabExecutionRequest
from ragcore.web.query_lab.models import (
AnswerabilityTrace,
ContextTrace,
EffectiveFilterTrace,
FusionTrace,
ParentExpansionTrace,
QueryLabQuery,
QueryLabTrace,
RerankTrace,
RevisionTrace,
)
from ragcore.web.query_lab.service import QueryLabService
WORKSPACE_ID = uuid4()
SPACE_ID = uuid4()
def _space() -> KnowledgeSpace:
return KnowledgeSpace(
id=SPACE_ID,
workspace_id=WORKSPACE_ID,
name="Docs",
slug="docs",
active=True,
trust_required=False,
data_policy=DataPolicy.LOCAL_ONLY,
retention_days=None,
)
def _user_grant(user_id, role: Role = Role.VIEWER) -> UserGrant:
return UserGrant(
id=uuid4(),
workspace_id=WORKSPACE_ID,
user_id=user_id,
knowledge_space_id=SPACE_ID,
role=role,
active=True,
)
class StaticInputsProvider:
def __init__(self, spaces=(), user_grants=()) -> None:
self._spaces = spaces
self._user_grants = user_grants
async def load(self, principal: Principal) -> AuthorizationInputs:
return AuthorizationInputs(
spaces=self._spaces, grants=(), user_grants=self._user_grants, application=None
)
class RecordingExecutor:
def __init__(self) -> None:
self.requests: list[QueryLabExecutionRequest] = []
async def execute(self, request: QueryLabExecutionRequest) -> QueryLabTrace:
self.requests.append(request)
return _trace(request)
def _trace(request: QueryLabExecutionRequest) -> QueryLabTrace:
return QueryLabTrace(
retrieval_run_id=request.retrieval_run_id,
query=request.query.query,
profile=request.query.profile,
effective_filters=EffectiveFilterTrace((), (), (), (), (), (), (), (), ()),
dense_candidates=(),
sparse_candidates=(),
fusion=FusionTrace(candidates=(), suppressed_count=0),
rerank=RerankTrace(state="skipped", degradation_code=None, candidates=(), elapsed_ms=0.0),
parent_expansion=ParentExpansionTrace(0, 0, 0),
context=ContextTrace(token_count=0, max_context_tokens=100, block_count=0, citation_ids=()),
answerability=AnswerabilityTrace(
status="not_answerable",
reasons=("insufficient_evidence",),
evidence_count=0,
document_diversity=0,
source_independence=0,
facet_coverage_ratio=0.0,
thresholds_version="v1",
),
citations=(),
timings_ms={},
revisions=RevisionTrace(None, None, "technical/v1", None, "v1"),
degraded=False,
degradation_codes=(),
)
def _service(executor: RecordingExecutor, spaces=(), user_grants=()) -> QueryLabService:
return QueryLabService(
authorization=AuthorizationService(),
retrieval_authorization=RetrievalAuthorizationService(),
inputs=StaticInputsProvider(spaces, user_grants),
executor=executor,
)
def _principal(role: Role) -> Principal:
return Principal(uuid4(), WORKSPACE_ID, PrincipalType.HUMAN, frozenset({role}))
def test_can_use_reflects_the_privilege_gate_without_side_effects() -> None:
service = _service(RecordingExecutor())
assert service.can_use(_principal(Role.SPACE_MANAGER)) is True
assert service.can_use(_principal(Role.PLATFORM_ADMIN)) is True
assert service.can_use(_principal(Role.VIEWER)) is False
assert service.can_use(_principal(Role.EDITOR)) is False
@pytest.mark.asyncio
async def test_viewer_is_denied_query_lab_even_with_a_valid_grant() -> None:
principal = _principal(Role.VIEWER)
executor = RecordingExecutor()
service = _service(executor, spaces=(_space(),), user_grants=(_user_grant(principal.id),))
with pytest.raises(PermissionDenied):
await service.run(principal, QueryLabQuery(query="test"))
assert executor.requests == []
@pytest.mark.asyncio
async def test_editor_is_denied_query_lab_even_with_a_valid_grant() -> None:
principal = _principal(Role.EDITOR)
executor = RecordingExecutor()
service = _service(executor, spaces=(_space(),), user_grants=(_user_grant(principal.id),))
with pytest.raises(PermissionDenied):
await service.run(principal, QueryLabQuery(query="test"))
assert executor.requests == []
@pytest.mark.asyncio
async def test_space_manager_with_a_grant_can_run_and_scope_is_resolved_server_side() -> None:
principal = _principal(Role.SPACE_MANAGER)
executor = RecordingExecutor()
service = _service(
executor,
spaces=(_space(),),
user_grants=(_user_grant(principal.id, role=Role.SPACE_MANAGER),),
)
trace = await service.run(
principal, QueryLabQuery(query="test", requested_space_ids=(SPACE_ID,))
)
assert trace.query == "test"
assert executor.requests[0].scope.effective_space_ids == frozenset({SPACE_ID})
@pytest.mark.asyncio
async def test_space_manager_without_any_grant_resolves_to_no_effective_spaces() -> None:
principal = _principal(Role.SPACE_MANAGER)
executor = RecordingExecutor()
service = _service(executor, spaces=(_space(),), user_grants=())
trace = await service.run(
principal, QueryLabQuery(query="test", requested_space_ids=(SPACE_ID,))
)
assert executor.requests == []
assert trace.effective_filters.effective_space_ids == ()
@pytest.mark.asyncio
async def test_manual_unauthorized_requested_space_is_never_included() -> None:
principal = _principal(Role.SPACE_MANAGER)
executor = RecordingExecutor()
service = _service(
executor,
spaces=(_space(),),
user_grants=(_user_grant(principal.id, role=Role.SPACE_MANAGER),),
)
unauthorized_space = uuid4()
trace = await service.run(
principal, QueryLabQuery(query="test", requested_space_ids=(unauthorized_space,))
)
# The requested space was never granted, so the resolved scope is empty
# and the executor is never invoked -- a client cannot widen access by
# simply asking for a different space id.
assert executor.requests == []
assert trace.effective_filters.effective_space_ids == ()
@pytest.mark.asyncio
async def test_platform_admin_with_a_grant_can_run_query_lab() -> None:
principal = _principal(Role.PLATFORM_ADMIN)
executor = RecordingExecutor()
service = _service(
executor, spaces=(_space(),), user_grants=(_user_grant(principal.id, role=Role.VIEWER),)
)
trace = await service.run(
principal, QueryLabQuery(query="test", requested_space_ids=(SPACE_ID,))
)
assert executor.requests[0].scope.effective_space_ids == frozenset({SPACE_ID})
assert trace.retrieval_run_id == executor.requests[0].retrieval_run_id