瀏覽代碼

fix: honor sync resolution completeness

Problem: execute_sync() discarded structured resolver completeness, so optional-only schemas could execute handlers even when resolution explicitly reported incomplete arguments.

Risk: the synchronous path still does not support LLM fallback; complete structured resolvers and compatible plain-dict resolvers must continue to execute unchanged.
zhenyu.hu 2 周之前
父節點
當前提交
2aba2dbc8c
共有 2 個文件被更改,包括 73 次插入1 次删除
  1. 6 1
      src/agent_lab/application/events/kernel.py
  2. 67 0
      tests/test_event_kernel.py

+ 6 - 1
src/agent_lab/application/events/kernel.py

@@ -175,7 +175,7 @@ class EventKernel:
         if early_result is not None:
             return early_result
         assert definition is not None
-        resolved, _, resolution_error = self._resolve_deterministic(
+        resolved, resolution_complete, resolution_error = self._resolve_deterministic(
             definition,
             request,
             context or EventExecutionContext(),
@@ -184,6 +184,11 @@ class EventKernel:
             return resolution_error
         assert resolved is not None
         validation = self._validate(definition, resolved.arguments)
+        if not resolution_complete and validation.status is None:
+            validation = _ValidationResult(
+                status=EventStatus.INVALID_ARGUMENTS,
+                error="event arguments incomplete",
+            )
         if validation.status is not None:
             return self._result(
                 definition,

+ 67 - 0
tests/test_event_kernel.py

@@ -362,6 +362,73 @@ async def test_plain_dict_resolution_remains_complete_for_optional_schema():
     assert fallback_calls == 0
 
 
+def test_sync_kernel_rejects_structured_incomplete_optional_arguments():
+    handler_calls = 0
+
+    def handler(request: EventRequest) -> dict[str, Any]:
+        nonlocal handler_calls
+        handler_calls += 1
+        return {"ok": True}
+
+    definition = _definition(
+        parameters={
+            "type": "object",
+            "properties": {"query": {"type": "string"}},
+        },
+        resolver=lambda request, context: EventArgumentResolution(
+            arguments={},
+            complete=False,
+        ),
+        handler=handler,
+    )
+
+    result = EventKernel(EventRegistry([definition])).execute_sync(
+        EventRequest(id="event-1", name="example.lookup"),
+        enabled_names=["example.lookup"],
+    )
+
+    assert result.status is EventStatus.INVALID_ARGUMENTS
+    assert result.error == "event arguments incomplete"
+    assert handler_calls == 0
+
+
+@pytest.mark.parametrize(
+    "resolver",
+    [
+        lambda request, context: EventArgumentResolution(
+            arguments={},
+            complete=True,
+        ),
+        lambda request, context: {},
+    ],
+)
+def test_sync_kernel_preserves_complete_compatible_resolvers(resolver: Any):
+    handler_calls = 0
+
+    def handler(request: EventRequest) -> dict[str, Any]:
+        nonlocal handler_calls
+        handler_calls += 1
+        return {"ok": True}
+
+    definition = _definition(
+        parameters={
+            "type": "object",
+            "properties": {"query": {"type": "string"}},
+        },
+        resolver=resolver,
+        handler=handler,
+    )
+
+    result = EventKernel(EventRegistry([definition])).execute_sync(
+        EventRequest(id="event-1", name="example.lookup"),
+        enabled_names=["example.lookup"],
+    )
+
+    assert result.status is EventStatus.SUCCESS
+    assert result.payload == {"ok": True}
+    assert handler_calls == 1
+
+
 @pytest.mark.asyncio
 async def test_provider_resolved_arguments_are_not_rewritten_or_fallen_back():
     resolver_calls = 0