From 2ef7719b028eba037778556bbbcd54b499fee8ab Mon Sep 17 00:00:00 2001 From: Jyong Date: Fri, 21 Aug 2026 03:10:14 -0400 Subject: [PATCH] fix(knowledge-fs): allow replay evidence detail query --- api/knowledge-fs-contract.lock.json | 2 +- api/knowledge-fs-product-operations.json | 2 +- .../knowledge_fs/product_operations.py | 4 +- .../test_knowledge_fs_product_operations.py | 1 + .../test_knowledge_fs_product_remote_http.py | 37 +++++++++++++++++++ 5 files changed, 43 insertions(+), 3 deletions(-) diff --git a/api/knowledge-fs-contract.lock.json b/api/knowledge-fs-contract.lock.json index 1d52148cdfd..1ed65e156ab 100644 --- a/api/knowledge-fs-contract.lock.json +++ b/api/knowledge-fs-contract.lock.json @@ -4,6 +4,6 @@ "openapiSha256": "2cf348c68bbe65dd51bbde9a0a4f91398beeebd79e89e9288c9386b26ae09796", "capabilityV2AuthManifestSha256": "fc0a47e23cce12544882f0298522b4933002e892b84ce1815df7e81d36a7a0c7", "capabilityV2AuthTestVectorSha256": "ae0de37b1ff05c40f905cf17a7b410d8971acacf64db07d5ee3d6fecfa559ce3", - "productOperationManifestSha256": "11f801a41805b250f17766935ee9cc7fc93c3ef6489503ba6297673b73362b7d", + "productOperationManifestSha256": "32f305d86525f31eb07a6aef1f72d286d53991f1f8ed1f9d6e52d37cf4561116", "productOperationGapManifestSha256": "332c80165bd5cf8e79bc511374dde771a8175510404db603a8067f1a22d36df8" } diff --git a/api/knowledge-fs-product-operations.json b/api/knowledge-fs-product-operations.json index 7672aec5790..05f2fdc5377 100644 --- a/api/knowledge-fs-product-operations.json +++ b/api/knowledge-fs-product-operations.json @@ -98,7 +98,7 @@ {"productOperationId":"updateQualityBadCase","kfsOperationId":"updateQualityBadCase","method":"PATCH","path":"/knowledge-spaces/{id}/quality/bad-cases/{badCaseId}","action":"quality.write","resource":"knowledge_space","transport":"json","stream":{"productKind":"json","kfsResponseKind":"buffered"},"limits":{"productMaxRequestBytes":65536,"productMaxResponseBytes":524288,"kfsMaxResponseBytes":1048576}}, {"productOperationId":"createQualityReplay","kfsOperationId":"createQualityReplay","method":"POST","path":"/knowledge-spaces/{id}/quality/replay-runs","action":"quality.write","resource":"knowledge_space","transport":"json","stream":{"productKind":"json","kfsResponseKind":"buffered"},"limits":{"productMaxRequestBytes":65536,"productMaxResponseBytes":4194304,"kfsMaxResponseBytes":4194304}}, {"productOperationId":"listQualityReplays","kfsOperationId":"listQualityReplays","method":"GET","path":"/knowledge-spaces/{id}/quality/replay-runs","action":"quality.read","resource":"knowledge_space","transport":"json","stream":{"productKind":"json","kfsResponseKind":"buffered"},"limits":{"productMaxRequestBytes":16384,"productMaxResponseBytes":4194304,"kfsMaxResponseBytes":4194304}}, - {"productOperationId":"getQualityReplay","kfsOperationId":"getQualityReplay","method":"GET","path":"/knowledge-spaces/{id}/quality/replay-runs/{runId}","action":"quality.read","resource":"knowledge_space","transport":"json","stream":{"productKind":"json","kfsResponseKind":"buffered"},"limits":{"productMaxRequestBytes":0,"productMaxResponseBytes":4194304,"kfsMaxResponseBytes":4194304}}, + {"productOperationId":"getQualityReplay","kfsOperationId":"getQualityReplay","method":"GET","path":"/knowledge-spaces/{id}/quality/replay-runs/{runId}","action":"quality.read","resource":"knowledge_space","transport":"json","stream":{"productKind":"json","kfsResponseKind":"buffered"},"limits":{"productMaxRequestBytes":16384,"productMaxResponseBytes":4194304,"kfsMaxResponseBytes":4194304}}, {"productOperationId":"getQualityBadCaseTraceReference","kfsOperationId":"getQualityBadCaseTraceReference","method":"GET","path":"/knowledge-spaces/{id}/quality/bad-cases/{badCaseId}/trace-reference","action":"quality.read","resource":"knowledge_space","transport":"json","stream":{"productKind":"json","kfsResponseKind":"buffered"},"limits":{"productMaxRequestBytes":0,"productMaxResponseBytes":65536,"kfsMaxResponseBytes":1048576}}, {"productOperationId":"listTraces","kfsOperationId":"listKnowledgeSpaceQualityTraces","method":"GET","path":"/knowledge-spaces/{id}/quality/traces","action":"quality.traces.list","resource":"knowledge_space","transport":"json","stream":{"productKind":"json","kfsResponseKind":"buffered"},"limits":{"productMaxRequestBytes":16384,"productMaxResponseBytes":4194304,"kfsMaxResponseBytes":1048576}}, {"productOperationId":"getTrace","kfsOperationId":"getAnswerTrace","method":"GET","path":"/queries/{traceId}","action":"queries.read","resource":"query","transport":"json","stream":{"productKind":"json","kfsResponseKind":"buffered"},"limits":{"productMaxRequestBytes":16384,"productMaxResponseBytes":2097152,"kfsMaxResponseBytes":1048576}}, diff --git a/api/services/knowledge_fs/product_operations.py b/api/services/knowledge_fs/product_operations.py index ce22319e9a2..ebdb9cec49a 100644 --- a/api/services/knowledge_fs/product_operations.py +++ b/api/services/knowledge_fs/product_operations.py @@ -1152,7 +1152,9 @@ KNOWLEDGE_FS_PRODUCT_OPERATIONS: Final[MappingProxyType[str, KnowledgeFSProductO "/knowledge-spaces/{id}/quality/replay-runs/{runId}", "json", resource_resolver="knowledge_space", - max_request_bytes=0, + # The optional evidenceItemId detail selector is transported as a query string. + # Keep the same bounded query allowance as the replay list operation. + max_request_bytes=16 * 1024, max_response_bytes=4 * 1024 * 1024, stream_kind="json", ), diff --git a/api/tests/unit_tests/services/test_knowledge_fs_product_operations.py b/api/tests/unit_tests/services/test_knowledge_fs_product_operations.py index 54e0fa1b7b9..d284ac4f2f9 100644 --- a/api/tests/unit_tests/services/test_knowledge_fs_product_operations.py +++ b/api/tests/unit_tests/services/test_knowledge_fs_product_operations.py @@ -154,6 +154,7 @@ def test_ready_product_operations_exactly_match_capability_method_path_and_actio assert product_operation.max_response_bytes > 0 assert KNOWLEDGE_FS_PRODUCT_OPERATIONS["createDocument"].max_request_bytes == 15 * 1024 * 1024 + assert KNOWLEDGE_FS_PRODUCT_OPERATIONS["getQualityReplay"].max_request_bytes == 16 * 1024 assert KNOWLEDGE_FS_PRODUCT_OPERATIONS["importSourceWorkflow"].max_request_bytes == 4 * 1024 * 1024 assert KNOWLEDGE_FS_PRODUCT_OPERATIONS["uploadSmallFile"].max_request_bytes == 8 * 1024 * 1024 diff --git a/api/tests/unit_tests/services/test_knowledge_fs_product_remote_http.py b/api/tests/unit_tests/services/test_knowledge_fs_product_remote_http.py index fef51089589..7a8608da197 100644 --- a/api/tests/unit_tests/services/test_knowledge_fs_product_remote_http.py +++ b/api/tests/unit_tests/services/test_knowledge_fs_product_remote_http.py @@ -60,6 +60,43 @@ def test_remote_client_builds_capability_only_headers(monkeypatch: pytest.Monkey assert captured["follow_redirects"] is False +def test_remote_client_allows_quality_replay_evidence_detail_query( + monkeypatch: pytest.MonkeyPatch, +) -> None: + captured: dict[str, object] = {} + response = httpx.Response( + 200, + json={"id": "run-1", "items": []}, + headers={"Content-Type": "application/json"}, + ) + + def fake_make_request(**kwargs): + captured.update(kwargs) + return response + + monkeypatch.setattr(ssrf_proxy, "make_request", fake_make_request) + monkeypatch.setattr(ssrf_proxy, "buffer_response", lambda response, **_: response) + client = HTTPKnowledgeFSProductRemoteClient(base_url="https://knowledge-fs.test", timeout_seconds=3) + evidence_item_id = "771a6d87-6421-458d-bd44-5127f016370d" + + result = client.execute_json( + KnowledgeFSRemoteJSONRequest( + operation_id="getQualityReplay", + method="GET", + path="/knowledge-spaces/space-1/quality/replay-runs/run-1", + namespace_id="tenant-1", + knowledge_space_id="space-1", + capability_token="capability-token", + trace_id="trace-1", + payload=None, + query=(("evidenceItemId", evidence_item_id),), + ) + ) + + assert result == {"id": "run-1", "items": []} + assert captured["params"] == (("evidenceItemId", evidence_item_id),) + + def test_remote_client_streams_sse_through_internal_capability_transport( monkeypatch: pytest.MonkeyPatch, ) -> None: