chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 13:12:33 +08:00
commit aacb60a4af
3387 changed files with 981307 additions and 0 deletions
@@ -0,0 +1,474 @@
"""Property tests for dispatch_legacy.py invariants.
Three properties verified across the corpus:
1. Idempotent status normalisation:
normalize(normalize(s)) == normalize(s) for any returned execution_status.
2. Artifact monotonicity:
len(ctx.published_artifacts) after dispatch >= before for any case.
3. First denial wins:
When two policies would each deny, only the first policy's error_class appears.
This is verified structurally against the multiple_policies_would_deny_first_wins
case (owner_only fires before denied_tools in the waterfall).
hypothesis is not available in this environment; cases are hand-rolled
parametrizations drawn from ALL_CASES plus targeted edge inputs.
"""
from __future__ import annotations
import json
from typing import Any
import pytest
from test_tools.dispatch_corpus import ALL_CASES, CorpusCase
from opensquilla.execution_status import normalize_execution_status
from opensquilla.tool_boundary import ToolCall
from opensquilla.tools.dispatch import build_tool_handler
from opensquilla.tools.registry import ToolRegistry
from opensquilla.tools.types import (
CallerKind,
InteractionMode,
ToolContext,
ToolSpec,
current_tool_context,
)
# ---------------------------------------------------------------------------
# Property 1: Idempotent status normalisation
# ---------------------------------------------------------------------------
#
# normalize_execution_status is a pure function. For any execution_status
# dict returned by dispatch, applying normalize again must produce the same
# result (i.e., it is already in canonical form after one application).
#
# We verify this against every corpus case result PLUS a set of hand-rolled
# raw dicts that cover all branches of normalize_execution_status.
_RAW_STATUS_SAMPLES: list[dict[str, Any]] = [
# Canonical forms — should be unchanged
{
"version": 1,
"status": "success",
"exit_code": None,
"timed_out": False,
"truncated": False,
"reason": None,
"source": "tool_runtime",
"preservation_class": "normal",
},
{
"version": 1,
"status": "error",
"exit_code": 1,
"timed_out": False,
"truncated": True,
"reason": "nonzero_exit",
"source": "adapter",
"preservation_class": "diagnostic",
},
{
"version": 1,
"status": "timeout",
"exit_code": None,
"timed_out": True,
"truncated": False,
"reason": "tool_timeout",
"source": "adapter",
"preservation_class": "diagnostic",
},
# Invalid status — normalises to unknown
{
"version": 1,
"status": "bogus_status",
"exit_code": None,
"timed_out": False,
"truncated": False,
"reason": None,
"source": "tool_runtime",
"preservation_class": "normal",
},
# Invalid source — normalises to unknown
{
"version": 1,
"status": "success",
"exit_code": None,
"timed_out": False,
"truncated": False,
"reason": None,
"source": "made_up_source",
"preservation_class": "normal",
},
# Invalid preservation_class — normalises to normal
{
"version": 1,
"status": "success",
"exit_code": None,
"timed_out": False,
"truncated": False,
"reason": None,
"source": "tool_runtime",
"preservation_class": "invalid_class",
},
# Non-dict input — normalises to unknown sentinel
"not a dict", # type: ignore[list-item]
None, # type: ignore[list-item]
42, # type: ignore[list-item]
# Empty dict — all fields default
{},
# approval_pending round-trip
{
"version": 1,
"status": "unknown",
"exit_code": None,
"timed_out": False,
"truncated": False,
"reason": "approval_pending",
"source": "tool_runtime",
"preservation_class": "ephemeral",
},
]
@pytest.mark.parametrize(
"raw_status",
_RAW_STATUS_SAMPLES,
ids=[f"sample_{i}" for i in range(len(_RAW_STATUS_SAMPLES))],
)
def test_normalize_execution_status_is_idempotent(raw_status: Any) -> None:
"""normalize(normalize(s)) == normalize(s) for any status input."""
once = normalize_execution_status(raw_status)
twice = normalize_execution_status(once)
assert once == twice, (
f"normalize is not idempotent: once={once!r}, twice={twice!r}"
)
@pytest.mark.parametrize(
"case",
ALL_CASES,
ids=[c.name for c in ALL_CASES],
)
@pytest.mark.asyncio
async def test_corpus_execution_status_is_already_normalised(case: CorpusCase) -> None:
"""Any execution_status returned by dispatch is already in normalised form."""
ctx = case.ctx_factory()
registry = case.registry_factory()
handler = build_tool_handler(
registry,
ctx,
known_skill_names=set(case.known_skill_names) if case.known_skill_names else None,
)
token = current_tool_context.set(None)
if case.setup is not None:
case.setup()
try:
result = await handler(case.tool_call)
finally:
current_tool_context.reset(token)
if case.teardown is not None:
case.teardown()
if result.execution_status is not None:
# Preflight rejections annotate the normalised status with exactly two
# diagnostic keys that normalize_execution_status does not carry.
# Assert their shape explicitly, then require the remainder to be in
# normalised form already.
comparable = dict(result.execution_status)
preflight_rejected = comparable.pop("preflight_rejected", None)
reason_code = comparable.pop("reason_code", None)
assert preflight_rejected in (None, True), (
f"[{case.name}] unexpected preflight_rejected value: "
f"{preflight_rejected!r}"
)
if preflight_rejected:
assert isinstance(reason_code, str) and reason_code, (
f"[{case.name}] preflight rejection missing reason_code: "
f"{result.execution_status!r}"
)
else:
assert reason_code is None, (
f"[{case.name}] reason_code present without preflight_rejected: "
f"{result.execution_status!r}"
)
re_normalised = normalize_execution_status(comparable)
assert comparable == re_normalised, (
f"[{case.name}] execution_status not already normalised: "
f"{result.execution_status!r} != {re_normalised!r}"
)
# ---------------------------------------------------------------------------
# Property 2: Artifact monotonicity
# ---------------------------------------------------------------------------
#
# For any dispatch call, len(ctx.published_artifacts) after >= before.
# Publishing artifacts is append-only — dispatch never removes items.
@pytest.mark.parametrize(
"case",
ALL_CASES,
ids=[c.name for c in ALL_CASES],
)
@pytest.mark.asyncio
async def test_artifact_list_never_shrinks_after_dispatch(case: CorpusCase) -> None:
"""len(ctx.published_artifacts) after dispatch >= before for any case."""
ctx = case.ctx_factory()
registry = case.registry_factory()
handler = build_tool_handler(
registry,
ctx,
known_skill_names=set(case.known_skill_names) if case.known_skill_names else None,
)
token = current_tool_context.set(None)
if case.setup is not None:
case.setup()
try:
before = len(ctx.published_artifacts) if ctx is not None else 0
await handler(case.tool_call)
after = len(ctx.published_artifacts) if ctx is not None else 0
finally:
current_tool_context.reset(token)
if case.teardown is not None:
case.teardown()
assert after >= before, (
f"[{case.name}] artifact list shrank: before={before}, after={after}"
)
# ---------------------------------------------------------------------------
# Property 3: First denial wins
# ---------------------------------------------------------------------------
#
# When multiple policies would deny a request, the waterfall fires them in
# document order. The corpus case `multiple_policies_would_deny_first_wins`
# sets up owner_only=True (lines 215231) + denied_tools (lines 233251).
# Owner-only fires first → OwnerOnly error_class, not PolicyDenied.
@pytest.mark.asyncio
async def test_first_denial_wins_owner_only_before_denied_tools() -> None:
"""owner_only check (lines 215-231) fires before denied_tools check (233-251)."""
registry = ToolRegistry()
async def _handler() -> str:
return "should not reach"
registry.register(
ToolSpec(name="double_deny_tool", description="test", parameters={}, owner_only=True),
_handler,
)
ctx = ToolContext(
is_owner=False,
caller_kind=CallerKind.AGENT,
interaction_mode=InteractionMode.INTERACTIVE,
agent_id="main",
session_key="agent:main:corpus",
denied_tools={"double_deny_tool"},
)
handler = build_tool_handler(registry, ctx)
token = current_tool_context.set(None)
try:
result = await handler(
ToolCall(
tool_use_id="tc-first-wins",
tool_name="double_deny_tool",
arguments={},
)
)
finally:
current_tool_context.reset(token)
assert result.is_error is True
payload = json.loads(result.content)
# OwnerOnly is the FIRST check; denied_tools is second.
# If denied_tools fired first we'd see PolicyDenied.
assert payload["error_class"] == "OwnerOnly", (
f"Expected OwnerOnly (first denial wins), got {payload['error_class']!r}. "
"This indicates the waterfall order has changed — update the corpus."
)
@pytest.mark.asyncio
async def test_first_denial_wins_denied_tools_before_private_memory() -> None:
"""denied_tools check (lines 233-251) fires before private_memory check (253-270).
Configure ctx with memory_get in denied_tools AND SUBAGENT caller (which
triggers private_memory_read_tool_denied). denied_tools fires first → PolicyDenied
with reason=denied. If private_memory fired first we'd get the same error_class
(PolicyDenied) but the log reason differs — we inspect log events.
"""
import structlog.testing
registry = ToolRegistry()
async def _handler() -> str:
return "should not reach"
registry.register(
ToolSpec(name="memory_get", description="test", parameters={}),
_handler,
)
ctx = ToolContext(
is_owner=True,
caller_kind=CallerKind.SUBAGENT,
interaction_mode=InteractionMode.UNATTENDED,
agent_id="sub1",
session_key="subagent:sub1:corpus",
denied_tools={"memory_get"}, # explicit deny fires at line 234
)
handler = build_tool_handler(registry, ctx)
token = current_tool_context.set(None)
try:
with structlog.testing.capture_logs() as captured:
result = await handler(
ToolCall(
tool_use_id="tc-deny-before-privmem",
tool_name="memory_get",
arguments={},
)
)
finally:
current_tool_context.reset(token)
assert result.is_error is True
# Both policies produce PolicyDenied — distinguish by log reason field
block_events = [
e for e in captured if e.get("event") == "dispatch.defense_in_depth_block"
]
assert block_events, "Expected at least one defense_in_depth_block log event"
# denied_tools fires first: reason="denied" (line 240)
# private_memory fires second: reason="private_memory_scope" (line 257)
first_reason = block_events[0].get("reason")
assert first_reason == "denied", (
f"Expected first block reason='denied' (denied_tools check), got {first_reason!r}. "
"Waterfall order may have changed."
)
@pytest.mark.asyncio
async def test_session_search_is_denied_by_private_memory_scope_for_subagents() -> None:
import structlog.testing
registry = ToolRegistry()
async def _handler() -> str:
return "should not reach"
registry.register(
ToolSpec(name="session_search", description="test", parameters={}),
_handler,
)
ctx = ToolContext(
is_owner=True,
caller_kind=CallerKind.SUBAGENT,
interaction_mode=InteractionMode.UNATTENDED,
agent_id="sub1",
session_key="subagent:agent:main:parent",
)
handler = build_tool_handler(registry, ctx)
token = current_tool_context.set(None)
try:
with structlog.testing.capture_logs() as captured:
result = await handler(
ToolCall(
tool_use_id="tc-subagent-session-search",
tool_name="session_search",
arguments={"query": "needle"},
)
)
finally:
current_tool_context.reset(token)
assert result.is_error is True
assert "not available in this context" in result.content
assert any(
event.get("reason") == "private_memory_scope"
for event in captured
if event.get("event") == "dispatch.defense_in_depth_block"
)
# ---------------------------------------------------------------------------
# Property 4: Contextvar set during handler call (HIGH 2)
# ---------------------------------------------------------------------------
#
# dispatch_legacy sets current_tool_context to effective_ctx before invoking
# the registered handler (line 343) and resets it in the finally block (line 492).
# Dispatch must honour the same contract so nested tool calls see their parent context.
#
# The _contextvar_reading_registry helper captures current_tool_context.get()
# mid-execution into registry._corpus_captured. This test asserts that captured
# value IS effective_ctx — not None, not some other context.
@pytest.mark.asyncio
async def test_contextvar_is_effective_ctx_during_handler_call() -> None:
"""current_tool_context.get() inside a handler equals effective_ctx (legacy line 343).
Rationale: dispatch could run without setting the contextvar before dispatch and
the existing ``contextvar_must_be_none_after`` assertion would still pass
(None → dispatch → None is indistinguishable from None → set → dispatch → reset → None
if we only check after the fact). This test closes that gap by asserting the
value seen *inside* the handler matches the context the handler was built for.
"""
registry = ToolRegistry()
captured_during: list[ToolContext | None] = []
async def _ctx_reader() -> str:
captured_during.append(current_tool_context.get())
return "done"
registry.register(
ToolSpec(name="ctx_reader", description="ctx_reader", parameters={}),
_ctx_reader,
)
expected_ctx = ToolContext(
is_owner=True,
caller_kind=CallerKind.AGENT,
interaction_mode=InteractionMode.INTERACTIVE,
agent_id="main",
session_key="agent:main:ctxvar-test",
)
handler = build_tool_handler(registry, expected_ctx)
# Start with contextvar unset (mimics normal agent loop entry)
token = current_tool_context.set(None)
try:
result = await handler(
ToolCall(tool_use_id="tc-ctxvar-during", tool_name="ctx_reader", arguments={})
)
finally:
current_tool_context.reset(token)
# Handler must have been called exactly once
assert len(captured_during) == 1, (
f"Expected handler to be called once, got {len(captured_during)} captured values"
)
# The value seen inside the handler must be the effective context, not None
assert captured_during[0] is not None, (
"current_tool_context.get() was None inside the handler — "
"dispatch did not set the contextvar before calling the handler (line 343 contract)."
)
assert captured_during[0] is expected_ctx, (
f"current_tool_context.get() inside handler was {captured_during[0]!r}, "
f"expected {expected_ctx!r}. "
"Dispatch must set current_tool_context to effective_ctx before invoking the handler."
)
# After dispatch the contextvar must be reset to None (finally-block contract)
assert current_tool_context.get() is None, (
"current_tool_context was not reset to None after dispatch — finally-block leak."
)
assert result.is_error is False