diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9b87610..d36a17a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -123,7 +123,7 @@ jobs: pip install -c constraints.txt -r requirements-dev.txt - name: Run unit tests run: | - PYTHONPATH=. pytest tests/test_unit.py tests/test_worker_fallback.py tests/test_supporting_components.py tests/test_resource_bindings.py --cov=execution_engine --cov-report=term-missing --cov-report=xml + PYTHONPATH=. pytest tests/test_unit.py tests/test_worker_fallback.py tests/test_supporting_components.py --cov=execution_engine --cov-report=term-missing --cov-report=xml python3 scripts/check-contracts.py python3 scripts/check-harness.py - name: Upload coverage artifact diff --git a/Taskfile.yml b/Taskfile.yml index 2b32f1c..44d0ec9 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -58,7 +58,7 @@ tasks: desc: Run unit tests cmds: - task python:check - - "PYTHONPATH=. {{.PYTHON}} -m pytest tests/test_unit.py tests/test_worker_fallback.py tests/test_supporting_components.py tests/test_tool_context.py tests/test_remediation.py tests/test_worker_approval_resume.py tests/test_resource_bindings.py tests/test_transcript_contract.py tests/test_react_transcript.py tests/test_keyless_eval_manifest.py" + - "PYTHONPATH=. {{.PYTHON}} -m pytest tests/test_unit.py tests/test_worker_fallback.py tests/test_supporting_components.py tests/test_tool_context.py tests/test_remediation.py tests/test_worker_approval_resume.py tests/test_transcript_contract.py tests/test_react_transcript.py tests/test_keyless_eval_manifest.py" keyless-eval: desc: Measure provider-native agent scenarios without credentials or TCP connections diff --git a/constraints.txt b/constraints.txt index bb7b842..5714b8f 100644 --- a/constraints.txt +++ b/constraints.txt @@ -5,7 +5,6 @@ uvicorn==0.46.0 httpx==0.28.1 pydantic==2.13.4 pydantic-settings==2.14.2 -rfc8785==0.1.4 tenacity==9.1.4 anyio==4.13.0 prometheus-client==0.25.0 diff --git a/docs/contracts/manifest.json b/docs/contracts/manifest.json index 44c9e6d..a2662d1 100644 --- a/docs/contracts/manifest.json +++ b/docs/contracts/manifest.json @@ -13,7 +13,6 @@ "assistant?.{targetType?,instructions}", "policy.{max_runtime_ms,max_output_tokens,budget_cents,max_steps,max_tool_calls,max_duplicate_tool_calls}", "context.{endpoint,max_context_tokens}", - "resources.{prompt_digest,binding_digest,resolved_at,bindings[].{binding_id,type,resource_id,provider,provider_version,workspace_id,label_snapshot,source,operations,context_mode,provider_data?}}", "llm.{provider,model,temperature,mode,reasoning.{summary_mode,effort},gateway.{url,token,request_timeout_ms}}", "tools.{tool_registry_version,allowed_tools,allowed_tool_refs[].{server_id,tool_name},native_tools,platform_functions[].{id,model_alias},tool_specs[].{server_id?,tool_ref?},referenced_tools[].{name,label,server_id?,tool_name?},write_unavailable_reason?,gateway.{url,token},confirmation_required_for_write,approval_timeout_seconds}", "skills?.{contract_version,entries[].{ref,skill_id,name,description,file_count,total_bytes},referenced_refs[],load_endpoint}", @@ -21,7 +20,6 @@ "tracing" ], "contextFields": ["messages", "summaries", "attachments", "target_insights.retrieval_status", "target_insights.snippets[]"], - "promptResourceIntegrity": "The execution engine rejects duplicate bindings, oversized or malformed claims, and any binding array whose canonical SHA-256 digest differs from resources.binding_digest.", "eventFrameFields": ["schema_version", "run_id", "seq", "ts", "type", "payload"], "approvalRequestFields": ["toolCallId", "toolName", "toolRef.{serverId,toolName}", "summary?", "arguments", "continuation?"], "approvalExecutionStartedResponseFields": ["approval", "approvalReceipt"], diff --git a/execution_engine/models.py b/execution_engine/models.py index 3240363..04f5c36 100644 --- a/execution_engine/models.py +++ b/execution_engine/models.py @@ -1,10 +1,8 @@ """Pydantic models for API requests, responses, and internal data structures.""" -import hashlib from datetime import UTC, datetime from typing import Annotated, Any, Dict, List, Literal, Optional, Union -import rfc8785 from pydantic import BaseModel, ConfigDict, Field, RootModel, model_validator from execution_engine.examples import ( @@ -172,76 +170,6 @@ class ContextConfig(BaseModel): max_context_tokens: int -class ResourceBinding(BaseModel): - """An exact prompt resource binding frozen by the control plane.""" - - binding_id: str - type: str - resource_id: str - provider: str - provider_version: str - workspace_id: str - label_snapshot: str - source: Literal["explicit", "implicit", "trigger"] - operations: List[str] - context_mode: Literal["inline", "tool", "routing_only"] - provider_data: Optional[Dict[str, Any]] = None - - @model_validator(mode="after") - def validate_operations(self): - if not self.operations or len(self.operations) > 64: - raise ValueError("resource binding operations must contain 1 to 64 entries") - if len(self.operations) != len(set(self.operations)) or any( - not operation.strip() for operation in self.operations - ): - raise ValueError("resource binding operations must be unique and non-empty") - return self - - model_config = ConfigDict(extra="forbid", strict=True) - - -class ResourceConfig(BaseModel): - """Prompt and binding integrity metadata for a Workflow run.""" - - prompt_digest: str - binding_digest: str - resolved_at: str - bindings: List[ResourceBinding] = Field(default_factory=list, max_length=64) - - @model_validator(mode="after") - def validate_integrity_metadata(self): - if len(self.prompt_digest) != 64 or len(self.binding_digest) != 64: - raise ValueError("resource digests must be SHA-256 hex strings") - if any(character not in "0123456789abcdef" for character in self.prompt_digest + self.binding_digest): - raise ValueError("resource digests must be lowercase SHA-256 hex strings") - binding_ids = [binding.binding_id for binding in self.bindings] - if len(binding_ids) != len(set(binding_ids)): - raise ValueError("resource binding IDs must be unique") - canonical = [] - for binding in self.bindings: - value = { - "bindingId": binding.binding_id, - "type": binding.type, - "resourceId": binding.resource_id, - "provider": binding.provider, - "providerVersion": binding.provider_version, - "workspaceId": binding.workspace_id, - "labelSnapshot": binding.label_snapshot, - "source": binding.source, - "operations": binding.operations, - "contextMode": binding.context_mode, - } - if binding.provider_data is not None: - value["providerData"] = binding.provider_data - canonical.append(value) - actual = hashlib.sha256(rfc8785.dumps(canonical)).hexdigest() - if actual != self.binding_digest: - raise ValueError("binding_digest does not match bindings") - return self - - model_config = ConfigDict(extra="forbid", strict=True) - - class GatewayConfig(BaseModel): """Configuration for the Execution Gateway.""" @@ -339,7 +267,6 @@ class ExecutionSnapshot(BaseModel): scope: Scope policy: Policy context: ContextConfig - resources: Optional[ResourceConfig] = None llm: LLMConfig tools: ToolConfig assistant: Optional[AssistantConfig] = None @@ -347,14 +274,6 @@ class ExecutionSnapshot(BaseModel): routing: Dict[str, Any] tracing: Dict[str, Any] - @model_validator(mode="after") - def validate_resource_scope(self): - if self.resources and any( - binding.workspace_id != self.scope.workspace_id for binding in self.resources.bindings - ): - raise ValueError("resource bindings must match the run workspace") - return self - model_config = ConfigDict(extra="forbid") @@ -395,7 +314,6 @@ class ContextPackage(BaseModel): messages: List[Message] summaries: List[Any] = [] attachments: List[Any] = [] - resources: List[Dict[str, Any]] = [] target_insights: TargetInsightsContext | None = None diff --git a/requirements.lock b/requirements.lock index 0350c25..def9c31 100644 --- a/requirements.lock +++ b/requirements.lock @@ -210,12 +210,6 @@ redis==7.4.0 \ # via # -c constraints.txt # -r requirements.txt -rfc8785==0.1.4 \ - --hash=sha256:520d690b448ecf0703691c76e1a34a24ddcd4fc5bc41d589cb7c58ec651bcd48 \ - --hash=sha256:e545841329fe0eee4f6a3b44e7034343100c12b4ec566dc06ca9735681deb4da - # via - # -c constraints.txt - # -r requirements.txt starlette==1.3.1 \ --hash=sha256:05d0213193f2fbaae60e2ecb593b4add4262ad4e46536b54abe36f11a71724e0 \ --hash=sha256:c7372aae11c3c3f26a42df7bd626cec2f47d03483d261d369516a615a53714c6 diff --git a/requirements.txt b/requirements.txt index 30928e6..62b8b20 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,4 +7,3 @@ tenacity anyio prometheus-client redis -rfc8785==0.1.4 diff --git a/tests/fixtures/resource-binding-digest-conformance.json b/tests/fixtures/resource-binding-digest-conformance.json deleted file mode 100644 index 57bdee4..0000000 --- a/tests/fixtures/resource-binding-digest-conformance.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "version": 1, - "bindings": [ - { - "bindingId": "prb_conformance_1", - "type": "artifact", - "resourceId": "artifact-1", - "provider": "test.artifact", - "providerVersion": "1", - "workspaceId": "workspace-1", - "labelSnapshot": "Café report", - "source": "explicit", - "operations": ["read"], - "contextMode": "tool", - "providerData": { - "score": 1.5, - "coordinates": [1, 2], - "enabled": true - } - } - ], - "sha256": "ebca583f4ae4d9ae4b00620b87f302103e997db0b416155538d0a83da40ed149" -} diff --git a/tests/test_resource_bindings.py b/tests/test_resource_bindings.py deleted file mode 100644 index f57789e..0000000 --- a/tests/test_resource_bindings.py +++ /dev/null @@ -1,65 +0,0 @@ -import json -from pathlib import Path - -import pytest -from pydantic import ValidationError - -from execution_engine.models import ResourceBinding, ResourceConfig - -VECTOR = json.loads( - (Path(__file__).parent / "fixtures/resource-binding-digest-conformance.json").read_text() -) - - -def binding(): - value = VECTOR["bindings"][0] - return { - "binding_id": value["bindingId"], - "type": value["type"], - "resource_id": value["resourceId"], - "provider": value["provider"], - "provider_version": value["providerVersion"], - "workspace_id": value["workspaceId"], - "label_snapshot": value["labelSnapshot"], - "source": value["source"], - "operations": value["operations"], - "context_mode": value["contextMode"], - "provider_data": value["providerData"], - } - - -def test_execution_snapshot_verifies_generic_resource_binding_digest(): - value = binding() - resource = ResourceConfig( - prompt_digest="a" * 64, - binding_digest=VECTOR["sha256"], - resolved_at="2026-07-20T00:00:00Z", - bindings=[value], - ) - assert resource.bindings[0].resource_id == "artifact-1" - - -def test_execution_snapshot_rejects_binding_tampering(): - value = binding() - with pytest.raises(ValidationError, match="does not match"): - ResourceConfig( - prompt_digest="a" * 64, - binding_digest="0" * 64, - resolved_at="2026-07-20T00:00:00Z", - bindings=[value], - ) - - -@pytest.mark.parametrize( - "changes", - [ - {"operations": []}, - {"operations": ["read", "read"]}, - {"operations": [""]}, - {"ignored": True}, - ], -) -def test_resource_binding_rejects_ambiguous_authority(changes): - value = {**binding(), **changes} - with pytest.raises(ValidationError): - ResourceBinding.model_validate(value) diff --git a/tests/test_unit.py b/tests/test_unit.py index abe5525..aff5b4d 100644 --- a/tests/test_unit.py +++ b/tests/test_unit.py @@ -1268,7 +1268,7 @@ async def test_coordination_functions_reject_unknown_arguments_before_orchestrat { "capabilityId": "infrastructure.diagnostics.read", "taskPrompt": "Inspect the infrastructure", - "resourceBinding": {"resourceId": "resource-1"}, + "unexpected": True, }, call_id="call-delegate-1", )