feat(server): integrate MCP queries and investigation messaging
Add project-bound OAuth MCP queries and controlled analysis with scoped evidence, limits, timeouts and a nearby-model spatial query using EPSG:4326 inputs. Default nearby searches to 200 meters and five results. Integrate investigation messaging/presentation, health/readiness and typed history; remove duplicate web search and fix online simulation and compressed baseline writes. Include project migrations 023-025; keep local credentials and demo seeding excluded. Validation: 319 focused pytest checks; real PostGIS temporary-table regression; read-only Jinqian bridge radius comparison; cross-language MCP discovery/calls.
This commit is contained in:
@@ -241,12 +241,14 @@ async def test_sample_sync_rejects_cross_project_data_before_writing():
|
||||
|
||||
@pytest.mark.anyio
|
||||
@pytest.mark.parametrize("targeted", [False, True])
|
||||
@pytest.mark.parametrize("order_revision", [1, 2])
|
||||
@pytest.mark.parametrize(
|
||||
("investigation_status", "expected_feedback_count", "routes_to_investigation"),
|
||||
[
|
||||
("waiting_human", 2, True),
|
||||
("queued", 2, True),
|
||||
("completed", 1, False),
|
||||
("completed", 2, True),
|
||||
("cancelled", 1, False),
|
||||
],
|
||||
)
|
||||
async def test_created_work_order_receipt_preserves_projection_and_routes_active_feedback(
|
||||
@@ -254,6 +256,7 @@ async def test_created_work_order_receipt_preserves_projection_and_routes_active
|
||||
expected_feedback_count,
|
||||
routes_to_investigation,
|
||||
targeted,
|
||||
order_revision,
|
||||
):
|
||||
project_id, order_id, investigation_id, projection_id = map(str, (uuid4(), uuid4(), uuid4(), uuid4()))
|
||||
received = []
|
||||
@@ -262,10 +265,14 @@ async def test_created_work_order_receipt_preserves_projection_and_routes_active
|
||||
order = {
|
||||
"project_id": project_id, "work_order_id": order_id, "origin": "synthetic",
|
||||
"created_by": str(uuid4()),
|
||||
"revision": 2, "summary": "采样已完成", "status": "completed", "updated_at": "2026-09-23T09:00:00Z",
|
||||
"revision": order_revision, "summary": "采样任务", "status": "completed" if order_revision > 1 else "open",
|
||||
"updated_at": "2026-09-23T09:00:00Z",
|
||||
"title": "合成采样工单", "priority": "normal",
|
||||
"work_order_type": "general", "created_at": "2026-09-23T08:00:00Z",
|
||||
"scheduled_at": None,
|
||||
"activity": ([{"sequence": 2, "occurred_at": "2026-09-23T09:00:00Z", "actor": "现场人员",
|
||||
"kind": "feedback", "title": "现场反馈", "detail": "复测余氯0.2mg/L"}]
|
||||
if order_revision > 1 else []),
|
||||
}
|
||||
if targeted:
|
||||
assert request.url.path.endswith(f"/work-orders/{order_id}")
|
||||
@@ -277,6 +284,8 @@ async def test_created_work_order_receipt_preserves_projection_and_routes_active
|
||||
payload = json.loads(request.content)
|
||||
assert payload["source_event_id"] == f"peripheral_sample:{order_id}"
|
||||
assert ("work_order_snapshot" in payload) == request.url.path.endswith(f"{projection_id}/external-feedback")
|
||||
if order_revision > 1:
|
||||
assert "复测余氯0.2mg/L" in payload["evidence"]["summary"]
|
||||
return httpx.Response(200, json={})
|
||||
if request.url.path.endswith(investigation_id):
|
||||
return httpx.Response(200, json={"actions": [{"kind": "create_work_order", "receipt": {
|
||||
@@ -287,7 +296,7 @@ async def test_created_work_order_receipt_preserves_projection_and_routes_active
|
||||
return httpx.Response(200, json={"items": [
|
||||
{"task_id": investigation_id, "task_type": "event_investigation", "status": investigation_status},
|
||||
{"task_id": projection_id, "task_type": "external_work_order", "source": {
|
||||
"external_id": f"peripheral_sample:{order_id}", "external_revision": 1,
|
||||
"external_id": f"peripheral_sample:{order_id}", "external_revision": 1 if order_revision > 1 else None,
|
||||
}},
|
||||
], "next_offset": None})
|
||||
settings = WorkerSettings("http://server.test", "http://agent.test", project_id, bearer="TEST_ONLY")
|
||||
@@ -295,9 +304,10 @@ async def test_created_work_order_receipt_preserves_projection_and_routes_active
|
||||
assert await sync_once(
|
||||
client, settings, "http://sample.test", CredentialProvider(settings),
|
||||
**({"work_order_ids": {order_id}} if targeted else {}),
|
||||
) == expected_feedback_count
|
||||
assert any(investigation_id in path for path in received) is routes_to_investigation
|
||||
) == (expected_feedback_count if order_revision > 1 else 1)
|
||||
assert any(investigation_id in path for path in received) is (routes_to_investigation and order_revision > 1)
|
||||
assert any(projection_id in path for path in received)
|
||||
assert received[-1].endswith(f"{projection_id}/external-feedback")
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
@@ -331,6 +341,61 @@ async def test_external_events_start_investigations_without_browser(event_kind):
|
||||
assert investigation["trigger"]["occurred_at"] == "2026-09-23T07:30:00Z"
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
@pytest.mark.parametrize("failure_target", ["parent", "projection"])
|
||||
async def test_child_feedback_retry_keeps_projection_cursor_behind_all_parent_deliveries(failure_target):
|
||||
project_id, order_id, parent_id, projection_id = map(str, (uuid4(), uuid4(), uuid4(), uuid4()))
|
||||
parent = {"task_id": parent_id, "task_type": "event_investigation", "status": "completed", "source": {}}
|
||||
projection = {"task_id": projection_id, "task_type": "external_work_order", "source": {
|
||||
"external_id": f"peripheral_sample:{order_id}", "external_revision": 1,
|
||||
}}
|
||||
order = {"project_id": project_id, "work_order_id": order_id, "origin": "synthetic",
|
||||
"revision": 2, "title": "采样", "summary": "复测通过", "status": "completed", "priority": "normal",
|
||||
"created_at": "2026-10-10T00:00:00Z", "updated_at": "2026-10-10T01:00:00Z", "scheduled_at": None,
|
||||
"work_order_type": "general"}
|
||||
failed = False
|
||||
accepted_parent_requests = set()
|
||||
detail_reads = []
|
||||
|
||||
def handler(request):
|
||||
nonlocal failed
|
||||
if request.url.host == "sample.test":
|
||||
return httpx.Response(200, json={"items": [order], "next_cursor": None})
|
||||
if request.method == "GET":
|
||||
if request.url.path.endswith(parent_id):
|
||||
detail_reads.append(parent_id)
|
||||
return httpx.Response(200, json={"actions": [{"kind": "create_work_order", "receipt": {
|
||||
"adapter": "peripheral_sample", "receipt_id": order_id,
|
||||
}}]})
|
||||
return httpx.Response(200, json={"items": [parent, projection], "next_offset": None})
|
||||
payload = json.loads(request.content)
|
||||
is_parent = request.url.path.endswith(f"{parent_id}/external-feedback")
|
||||
if not failed and is_parent == (failure_target == "parent"):
|
||||
failed = True
|
||||
return httpx.Response(503, json={})
|
||||
if is_parent:
|
||||
accepted_parent_requests.add(payload["request_id"])
|
||||
parent["status"] = "queued"
|
||||
else:
|
||||
assert accepted_parent_requests, "Projection must not advance before its associated investigation"
|
||||
projection["source"]["external_revision"] = payload["source_revision"]
|
||||
return httpx.Response(200, json={})
|
||||
|
||||
settings = WorkerSettings("http://server.test", "http://agent.test", project_id, bearer="TEST_ONLY")
|
||||
async with httpx.AsyncClient(transport=httpx.MockTransport(handler)) as client:
|
||||
credentials = CredentialProvider(settings)
|
||||
with pytest.raises(httpx.HTTPStatusError):
|
||||
await sync_once(client, settings, "http://sample.test", credentials)
|
||||
assert projection["source"]["external_revision"] == 1
|
||||
assert await sync_once(client, settings, "http://sample.test", credentials) == 2
|
||||
assert len(accepted_parent_requests) == 1
|
||||
assert projection["source"]["external_revision"] == 2
|
||||
parent["status"] = "completed"
|
||||
detail_reads.clear()
|
||||
assert await sync_once(client, settings, "http://sample.test", credentials) == 0
|
||||
assert detail_reads == [], "Unchanged source versions must not scan completed parent details forever"
|
||||
|
||||
|
||||
def test_snapshot_preserves_recorded_activity_without_legacy_subtasks():
|
||||
order = {
|
||||
"work_order_id": str(uuid4()), "revision": 4, "title": "合成预约", "summary": "仅测试过程同步",
|
||||
|
||||
Reference in New Issue
Block a user