agenthooksprotocol 0.7.0__tar.gz → 0.7.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {agenthooksprotocol-0.7.0/src/agenthooksprotocol.egg-info → agenthooksprotocol-0.7.1}/PKG-INFO +1 -1
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/pyproject.toml +1 -1
- agenthooksprotocol-0.7.1/src/agenthooksprotocol/_validator.py +342 -0
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/runtime.py +17 -10
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1/src/agenthooksprotocol.egg-info}/PKG-INFO +1 -1
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol.egg-info/SOURCES.txt +3 -1
- agenthooksprotocol-0.7.1/tests/test_validator_optimization.py +1319 -0
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/LICENSE +0 -0
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/README.md +0 -0
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/setup.cfg +0 -0
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/__init__.py +0 -0
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/_boundaries.py +0 -0
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/_content.py +0 -0
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/_diagnostics.py +0 -0
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/_elicitation.py +0 -0
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/_grants.py +0 -0
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/_hooks.py +0 -0
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/_host_metadata.py +0 -0
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/_interop_content.py +0 -0
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/_interop_hooks.py +0 -0
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/_models/__init__.py +0 -0
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/_models/capability.py +0 -0
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/_models/content.py +0 -0
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/_models/effect.py +0 -0
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/_models/event.py +0 -0
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/_models/registration.py +0 -0
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/_models/subscription.py +0 -0
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/_models/tool.py +0 -0
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/_models/transport.py +0 -0
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/_outcomes.py +0 -0
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/ahp-codegen.lock.json +0 -0
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/attachment.py +0 -0
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/auth.py +0 -0
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/candidate.py +0 -0
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/capability.py +0 -0
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/catalogue.py +0 -0
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/codec.py +0 -0
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/compaction.py +0 -0
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/content.py +0 -0
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/contract.py +0 -0
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/diagnostics.py +0 -0
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/effect.py +0 -0
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/elicitation.py +0 -0
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/event.py +0 -0
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/generated.py +0 -0
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/integrations/__init__.py +0 -0
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/integrations/pydantic.py +0 -0
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/interop.py +0 -0
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/lifecycle.py +0 -0
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/lifecycle_client.py +0 -0
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/lifecycle_server.py +0 -0
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/lineage.py +0 -0
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/observation_chain.py +0 -0
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/path.py +0 -0
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/permission.py +0 -0
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/py.typed +0 -0
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/registration.py +0 -0
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/response.py +0 -0
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/schemas.json +0 -0
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/server/__init__.py +0 -0
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/server/_admission.py +0 -0
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/server/_framing.py +0 -0
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/server/_handlers.py +0 -0
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/server/_json.py +0 -0
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/server/_slots.py +0 -0
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/server/asgi.py +0 -0
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/server/attachments.py +0 -0
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/server/hooks.py +0 -0
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/server/stdio.py +0 -0
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/state.py +0 -0
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/subscription.py +0 -0
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/tool.py +0 -0
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/transport.py +0 -0
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/transports/__init__.py +0 -0
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/transports/http.py +0 -0
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/transports/stdio.py +0 -0
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/wire.py +0 -0
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol.egg-info/dependency_links.txt +0 -0
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol.egg-info/requires.txt +0 -0
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol.egg-info/top_level.txt +0 -0
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/tests/test_attachments.py +0 -0
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/tests/test_catalogue.py +0 -0
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/tests/test_codec.py +0 -0
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/tests/test_compaction.py +0 -0
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/tests/test_compaction_wire.py +0 -0
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/tests/test_consumer_outcomes.py +0 -0
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/tests/test_content.py +0 -0
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/tests/test_content_lifetime.py +0 -0
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/tests/test_content_receipts.py +0 -0
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/tests/test_correlated_core.py +0 -0
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/tests/test_declared_contracts.py +0 -0
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/tests/test_elicitation_adapter.py +0 -0
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/tests/test_event_types.py +0 -0
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/tests/test_fixture_public.py +0 -0
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/tests/test_handwritten_surface.py +0 -0
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/tests/test_harness_identity.py +0 -0
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/tests/test_host_metadata.py +0 -0
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/tests/test_inline_messages.py +0 -0
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/tests/test_integration.py +0 -0
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/tests/test_integration_edges.py +0 -0
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/tests/test_interop.py +0 -0
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/tests/test_lifecycle.py +0 -0
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/tests/test_lifecycle_auth.py +0 -0
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/tests/test_observation.py +0 -0
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/tests/test_observation_chain_transport.py +0 -0
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/tests/test_owned_content.py +0 -0
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/tests/test_owned_content_hooks.py +0 -0
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/tests/test_package.py +0 -0
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/tests/test_public_content.py +0 -0
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/tests/test_public_contract_surface.py +0 -0
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/tests/test_public_examples.py +0 -0
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/tests/test_public_hooks.py +0 -0
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/tests/test_public_server.py +0 -0
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/tests/test_registration_auth.py +0 -0
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/tests/test_safety.py +0 -0
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/tests/test_schema_corrections.py +0 -0
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/tests/test_sdk_unification.py +0 -0
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/tests/test_server_catch_all.py +0 -0
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/tests/test_server_error_codes.py +0 -0
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/tests/test_session_manifest.py +0 -0
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/tests/test_shared_materialization.py +0 -0
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/tests/test_stdio_env.py +0 -0
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/tests/test_stdio_transport.py +0 -0
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/tests/test_structural_facade.py +0 -0
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/tests/test_subscribes.py +0 -0
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/tests/test_typed_capabilities.py +0 -0
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/tests/test_typed_contract_regressions.py +0 -0
- {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/tests/test_upload_planning.py +0 -0
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
"""Private, per-bundle oneOf pruning; never changes schemas or global validators."""
|
|
2
|
+
|
|
3
|
+
from collections import OrderedDict
|
|
4
|
+
from threading import Lock
|
|
5
|
+
|
|
6
|
+
from attrs import evolve as attrs_evolve, fields
|
|
7
|
+
from jsonschema import Draft202012Validator, exceptions, validators
|
|
8
|
+
from referencing import Registry
|
|
9
|
+
from referencing.exceptions import NoSuchResource, Unresolvable
|
|
10
|
+
from referencing.jsonschema import DRAFT202012
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class _UnsafePlan(Exception):
|
|
14
|
+
pass
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def canonical_backend(
|
|
18
|
+
schemas, registry, *, optimize_unions=True, cache_references=True
|
|
19
|
+
):
|
|
20
|
+
"""Lazily compile original canonical schemas, never caller form schemas.
|
|
21
|
+
|
|
22
|
+
Constraints are necessary conditions, not replacement validation. Missing
|
|
23
|
+
properties prove nothing. Unsafe branches remain unconditional candidates.
|
|
24
|
+
Schemas must remain unchanged after construction. All schema-only caches are
|
|
25
|
+
per backend and retain schema identities. Compiler caches include base URI;
|
|
26
|
+
the bounded runtime reference cache instead retains exact source resolvers
|
|
27
|
+
and complete resolution results, including dynamic scope and registry.
|
|
28
|
+
"""
|
|
29
|
+
plans = {}
|
|
30
|
+
safety = {}
|
|
31
|
+
extracted = {}
|
|
32
|
+
references = {}
|
|
33
|
+
owned = {id(schema): schema for schema in schemas}
|
|
34
|
+
indexed = set()
|
|
35
|
+
stock_one_of = Draft202012Validator.VALIDATORS["oneOf"]
|
|
36
|
+
stock_ref = Draft202012Validator.VALIDATORS["$ref"]
|
|
37
|
+
default_retrieve = getattr(Registry(), "_retrieve", None)
|
|
38
|
+
resolution_cache = OrderedDict()
|
|
39
|
+
resolution_lock = Lock()
|
|
40
|
+
compatibility_cache = OrderedDict()
|
|
41
|
+
compatibility_lock = Lock()
|
|
42
|
+
expected_graph = []
|
|
43
|
+
|
|
44
|
+
def deny_retrieval(uri):
|
|
45
|
+
raise NoSuchResource(ref=uri)
|
|
46
|
+
|
|
47
|
+
# Persistent registry copy shares existing resources; compiler lookups never
|
|
48
|
+
# invoke even a caller-provided retriever. Runtime resolution stays original.
|
|
49
|
+
compiler_registry = attrs_evolve(registry, retrieve=deny_retrieval)
|
|
50
|
+
|
|
51
|
+
def plan_key(schema, resolver):
|
|
52
|
+
# referencing has no public base-URI accessor. If it changes, disable
|
|
53
|
+
# pruning rather than sharing a plan across scopes.
|
|
54
|
+
base_uri = getattr(resolver, "_base_uri", None)
|
|
55
|
+
if isinstance(base_uri, str):
|
|
56
|
+
return id(schema), base_uri
|
|
57
|
+
return None
|
|
58
|
+
|
|
59
|
+
def child_resolver(schema, resolver):
|
|
60
|
+
return resolver.in_subresource(DRAFT202012.create_resource(schema))
|
|
61
|
+
|
|
62
|
+
def resolve(ref, resolver):
|
|
63
|
+
if not isinstance(ref, str):
|
|
64
|
+
raise _UnsafePlan
|
|
65
|
+
key = (getattr(resolver, "_base_uri", None), ref)
|
|
66
|
+
if key not in references:
|
|
67
|
+
try:
|
|
68
|
+
references[key] = resolver.lookup(ref)
|
|
69
|
+
except Exception:
|
|
70
|
+
references[key] = None
|
|
71
|
+
resolved = references[key]
|
|
72
|
+
if resolved is None:
|
|
73
|
+
raise _UnsafePlan
|
|
74
|
+
return resolved
|
|
75
|
+
|
|
76
|
+
def index_owned(schema):
|
|
77
|
+
if id(schema) in indexed:
|
|
78
|
+
return
|
|
79
|
+
indexed.add(id(schema))
|
|
80
|
+
owned[id(schema)] = schema
|
|
81
|
+
for resource in DRAFT202012.create_resource(schema).subresources():
|
|
82
|
+
index_owned(resource.contents)
|
|
83
|
+
|
|
84
|
+
def is_owned(schema, resolver):
|
|
85
|
+
if owned.get(id(schema)) is schema:
|
|
86
|
+
return True
|
|
87
|
+
key = plan_key(schema, resolver)
|
|
88
|
+
if key is None:
|
|
89
|
+
return False
|
|
90
|
+
try:
|
|
91
|
+
resource = resolve(key[1], resolver).contents
|
|
92
|
+
except _UnsafePlan:
|
|
93
|
+
return False
|
|
94
|
+
if owned.get(id(resource)) is resource:
|
|
95
|
+
# Index only a resource actually visited by validation, not all
|
|
96
|
+
# bundle documents. Definitions establish ownership, not safety.
|
|
97
|
+
index_owned(resource)
|
|
98
|
+
return owned.get(id(schema)) is schema
|
|
99
|
+
|
|
100
|
+
def safe_graph(schema, resolver, active):
|
|
101
|
+
if not isinstance(schema, dict):
|
|
102
|
+
return
|
|
103
|
+
key = plan_key(schema, resolver)
|
|
104
|
+
if key is None or id(schema) in active:
|
|
105
|
+
raise _UnsafePlan
|
|
106
|
+
record = safety.get(key)
|
|
107
|
+
if record is not None and record[0] is schema:
|
|
108
|
+
if not record[1]:
|
|
109
|
+
raise _UnsafePlan
|
|
110
|
+
return
|
|
111
|
+
try:
|
|
112
|
+
if (
|
|
113
|
+
"$dynamicRef" in schema
|
|
114
|
+
or "$recursiveRef" in schema
|
|
115
|
+
or any(
|
|
116
|
+
isinstance(branch, dict) and "$id" in branch
|
|
117
|
+
for branch in schema.get("oneOf", ())
|
|
118
|
+
)
|
|
119
|
+
or schema.get("$schema", Draft202012Validator.META_SCHEMA["$id"])
|
|
120
|
+
!= Draft202012Validator.META_SCHEMA["$id"]
|
|
121
|
+
):
|
|
122
|
+
raise _UnsafePlan
|
|
123
|
+
active = active | {id(schema)}
|
|
124
|
+
if "$ref" in schema:
|
|
125
|
+
resolved = resolve(schema["$ref"], resolver)
|
|
126
|
+
safe_graph(resolved.contents, resolved.resolver, active)
|
|
127
|
+
# Definition containers are inert. Follow their schemas only via
|
|
128
|
+
# actual refs, avoiding artificial cycles through unused definitions.
|
|
129
|
+
evaluated = {
|
|
130
|
+
key: value
|
|
131
|
+
for key, value in schema.items()
|
|
132
|
+
if key not in {"$defs", "definitions"}
|
|
133
|
+
}
|
|
134
|
+
for resource in DRAFT202012.create_resource(evaluated).subresources():
|
|
135
|
+
safe_graph(resource.contents, resolver.in_subresource(resource), active)
|
|
136
|
+
except _UnsafePlan:
|
|
137
|
+
safety[key] = schema, False
|
|
138
|
+
raise
|
|
139
|
+
safety[key] = schema, True
|
|
140
|
+
|
|
141
|
+
def constraints(schema, resolver):
|
|
142
|
+
if not isinstance(schema, dict):
|
|
143
|
+
return ()
|
|
144
|
+
key = plan_key(schema, resolver)
|
|
145
|
+
record = extracted.get(key)
|
|
146
|
+
if record is not None and record[0] is schema:
|
|
147
|
+
return record[1]
|
|
148
|
+
result = []
|
|
149
|
+
if isinstance(schema.get("const"), str):
|
|
150
|
+
result.append(((), schema["const"]))
|
|
151
|
+
if "$ref" in schema:
|
|
152
|
+
resolved = resolve(schema["$ref"], resolver)
|
|
153
|
+
result.extend(constraints(resolved.contents, resolved.resolver))
|
|
154
|
+
for branch in schema.get("allOf", ()):
|
|
155
|
+
result.extend(constraints(branch, child_resolver(branch, resolver)))
|
|
156
|
+
for name, subschema in schema.get("properties", {}).items():
|
|
157
|
+
result.extend(
|
|
158
|
+
((name,) + path, tag)
|
|
159
|
+
for path, tag in constraints(
|
|
160
|
+
subschema, child_resolver(subschema, resolver)
|
|
161
|
+
)
|
|
162
|
+
)
|
|
163
|
+
result = tuple(result)
|
|
164
|
+
extracted[key] = schema, result
|
|
165
|
+
return result
|
|
166
|
+
|
|
167
|
+
def prepare(schema, resolver):
|
|
168
|
+
# Stock initial branches enter $id scopes; remaining branches evolve
|
|
169
|
+
# without doing so. Never compile a union affected by that scope quirk.
|
|
170
|
+
if any(
|
|
171
|
+
isinstance(branch, dict) and "$id" in branch for branch in schema["oneOf"]
|
|
172
|
+
):
|
|
173
|
+
return None
|
|
174
|
+
branches = []
|
|
175
|
+
for branch in schema["oneOf"]:
|
|
176
|
+
try:
|
|
177
|
+
scoped = child_resolver(branch, resolver)
|
|
178
|
+
safe_graph(branch, scoped, set())
|
|
179
|
+
conditions = constraints(branch, scoped)
|
|
180
|
+
except _UnsafePlan:
|
|
181
|
+
conditions = ()
|
|
182
|
+
branches.append(conditions)
|
|
183
|
+
return tuple(branches)
|
|
184
|
+
|
|
185
|
+
def matches(instance, constraint, validator):
|
|
186
|
+
path, tag = constraint
|
|
187
|
+
for name in path:
|
|
188
|
+
if not validator.is_type(instance, "object") or name not in instance:
|
|
189
|
+
return None
|
|
190
|
+
instance = instance[name]
|
|
191
|
+
return instance == tag
|
|
192
|
+
|
|
193
|
+
def compatible_registry(actual):
|
|
194
|
+
if actual is None:
|
|
195
|
+
return False
|
|
196
|
+
with compatibility_lock:
|
|
197
|
+
record = compatibility_cache.get(id(actual))
|
|
198
|
+
if record is not None and record[0] is actual:
|
|
199
|
+
compatibility_cache.move_to_end(id(actual))
|
|
200
|
+
return record[1]
|
|
201
|
+
if not expected_graph:
|
|
202
|
+
expected = validators.SPECIFICATIONS.combine(registry).crawl()
|
|
203
|
+
expected_graph.append({uri: expected[uri].contents for uri in expected})
|
|
204
|
+
expected = expected_graph[0]
|
|
205
|
+
try:
|
|
206
|
+
crawled = actual.crawl()
|
|
207
|
+
compatible = len(crawled) == len(expected) and all(
|
|
208
|
+
uri in crawled and crawled[uri].contents is contents
|
|
209
|
+
for uri, contents in expected.items()
|
|
210
|
+
)
|
|
211
|
+
except Exception:
|
|
212
|
+
compatible = False
|
|
213
|
+
with compatibility_lock:
|
|
214
|
+
compatibility_cache[id(actual)] = actual, compatible
|
|
215
|
+
compatibility_cache.move_to_end(id(actual))
|
|
216
|
+
if len(compatibility_cache) > 256:
|
|
217
|
+
compatibility_cache.popitem(last=False)
|
|
218
|
+
return compatible
|
|
219
|
+
|
|
220
|
+
def one_of(validator, branches, instance, schema):
|
|
221
|
+
actual_registry = getattr(validator._resolver, "_registry", None)
|
|
222
|
+
retrieve = getattr(actual_registry, "_retrieve", None)
|
|
223
|
+
if (
|
|
224
|
+
getattr(validator, "_registry", None) is not registry
|
|
225
|
+
or default_retrieve is None
|
|
226
|
+
or retrieve is not default_retrieve
|
|
227
|
+
or validator._ref_resolver is not None
|
|
228
|
+
or not validator.is_type(instance, "object")
|
|
229
|
+
or not compatible_registry(actual_registry)
|
|
230
|
+
):
|
|
231
|
+
# Plans belong to the construction graph. Registry substitutions,
|
|
232
|
+
# extra resources, and stateful retrieval must retain stock behavior.
|
|
233
|
+
yield from stock_one_of(validator, branches, instance, schema)
|
|
234
|
+
return
|
|
235
|
+
plan = None
|
|
236
|
+
key = plan_key(schema, validator._resolver)
|
|
237
|
+
if (
|
|
238
|
+
key is not None
|
|
239
|
+
and validator._ref_resolver is None
|
|
240
|
+
and validator.is_type(instance, "object")
|
|
241
|
+
):
|
|
242
|
+
record = plans.get(key)
|
|
243
|
+
if record is None:
|
|
244
|
+
resolver = compiler_registry.resolver(base_uri=key[1])
|
|
245
|
+
plan = prepare(schema, resolver) if is_owned(schema, resolver) else None
|
|
246
|
+
record = plans[key] = schema, plan
|
|
247
|
+
if record[0] is schema:
|
|
248
|
+
plan = record[1]
|
|
249
|
+
if plan is not None:
|
|
250
|
+
candidates = []
|
|
251
|
+
for index, conditions in enumerate(plan):
|
|
252
|
+
if not any(
|
|
253
|
+
matches(instance, condition, validator) is False
|
|
254
|
+
for condition in conditions
|
|
255
|
+
):
|
|
256
|
+
candidates.append(index)
|
|
257
|
+
if len(candidates) < len(branches):
|
|
258
|
+
valid_count = 0
|
|
259
|
+
for index in candidates:
|
|
260
|
+
if valid_count:
|
|
261
|
+
# Match stock remaining-branch scope and traversal even
|
|
262
|
+
# after a second winner: a later branch may raise.
|
|
263
|
+
valid = validator.evolve(schema=branches[index]).is_valid(
|
|
264
|
+
instance
|
|
265
|
+
)
|
|
266
|
+
else:
|
|
267
|
+
errors = validator.descend(
|
|
268
|
+
instance, branches[index], schema_path=index
|
|
269
|
+
)
|
|
270
|
+
if plan[index]:
|
|
271
|
+
valid = next(errors, None) is None
|
|
272
|
+
else:
|
|
273
|
+
# Exhaust unsafe initial branches just like stock:
|
|
274
|
+
# refs after an earlier error must still raise.
|
|
275
|
+
valid = not list(errors)
|
|
276
|
+
if valid:
|
|
277
|
+
valid_count += 1
|
|
278
|
+
if valid_count == 1:
|
|
279
|
+
return
|
|
280
|
+
yield from stock_one_of(validator, branches, instance, schema)
|
|
281
|
+
|
|
282
|
+
def reference(validator, ref, instance, schema):
|
|
283
|
+
if validator._ref_resolver is not None or not isinstance(ref, str):
|
|
284
|
+
yield from stock_ref(validator, ref, instance, schema)
|
|
285
|
+
return
|
|
286
|
+
source = validator._resolver
|
|
287
|
+
key = id(source), ref
|
|
288
|
+
with resolution_lock:
|
|
289
|
+
record = resolution_cache.get(key)
|
|
290
|
+
if record is not None and record[0] is source:
|
|
291
|
+
resolution_cache.move_to_end(key)
|
|
292
|
+
resolved = record[1]
|
|
293
|
+
else:
|
|
294
|
+
resolved = None
|
|
295
|
+
if resolved is None:
|
|
296
|
+
try:
|
|
297
|
+
# Only resources already registered in this exact resolver's
|
|
298
|
+
# immutable registry are cacheable. Retrieval may be stateful.
|
|
299
|
+
probe = attrs_evolve(
|
|
300
|
+
source,
|
|
301
|
+
registry=attrs_evolve(source._registry, retrieve=deny_retrieval),
|
|
302
|
+
)
|
|
303
|
+
probe.lookup(ref)
|
|
304
|
+
except Exception:
|
|
305
|
+
yield from stock_ref(validator, ref, instance, schema)
|
|
306
|
+
return
|
|
307
|
+
try:
|
|
308
|
+
resolved = source.lookup(ref)
|
|
309
|
+
except Unresolvable as err:
|
|
310
|
+
# Match stock wrapping, without retrying a failed real lookup.
|
|
311
|
+
raise exceptions._WrappedReferencingError(err) from err
|
|
312
|
+
with resolution_lock:
|
|
313
|
+
resolution_cache[key] = source, resolved
|
|
314
|
+
resolution_cache.move_to_end(key)
|
|
315
|
+
if len(resolution_cache) > 256:
|
|
316
|
+
resolution_cache.popitem(last=False)
|
|
317
|
+
yield from validator.descend(
|
|
318
|
+
instance, resolved.contents, resolver=resolved.resolver
|
|
319
|
+
)
|
|
320
|
+
|
|
321
|
+
backend = validators.extend(
|
|
322
|
+
Draft202012Validator,
|
|
323
|
+
{
|
|
324
|
+
"oneOf": one_of if optimize_unions else stock_one_of,
|
|
325
|
+
"$ref": reference if cache_references else stock_ref,
|
|
326
|
+
},
|
|
327
|
+
)
|
|
328
|
+
|
|
329
|
+
def evolve(self, **changes):
|
|
330
|
+
# Preserve isolated backend across explicit 2020-12 $schema evolution,
|
|
331
|
+
# retaining stock dialect selection for other schema dialects.
|
|
332
|
+
schema = changes.setdefault("schema", self.schema)
|
|
333
|
+
target = validators.validator_for(schema, default=backend)
|
|
334
|
+
if target is Draft202012Validator:
|
|
335
|
+
target = backend
|
|
336
|
+
for field in fields(backend):
|
|
337
|
+
if field.init and field.alias not in changes:
|
|
338
|
+
changes[field.alias] = getattr(self, field.name)
|
|
339
|
+
return target(**changes)
|
|
340
|
+
|
|
341
|
+
backend.evolve = evolve
|
|
342
|
+
return backend
|
|
@@ -10,6 +10,7 @@ from referencing import Registry, Resource
|
|
|
10
10
|
from . import generated, _models as models
|
|
11
11
|
from .response import response_for_request
|
|
12
12
|
from .diagnostics import Code as DiagnosticCode
|
|
13
|
+
from ._validator import canonical_backend
|
|
13
14
|
|
|
14
15
|
|
|
15
16
|
__all__ = ["ProtocolError", "OperationCancelledError", "BackendRPCError", "Validator"]
|
|
@@ -66,12 +67,20 @@ class Validator:
|
|
|
66
67
|
registry = Registry().with_resources(
|
|
67
68
|
(s["$id"], Resource.from_contents(s)) for s in schemas.values()
|
|
68
69
|
)
|
|
70
|
+
backend = canonical_backend(schemas.values(), registry)
|
|
69
71
|
self.validators = {
|
|
70
|
-
n:
|
|
71
|
-
s, registry=registry, format_checker=FormatChecker()
|
|
72
|
-
)
|
|
72
|
+
n: backend(s, registry=registry, format_checker=FormatChecker())
|
|
73
73
|
for n, s in schemas.items()
|
|
74
74
|
}
|
|
75
|
+
self._fragments = {}
|
|
76
|
+
root = self.validators.get("mcp-elicitation.schema.json")
|
|
77
|
+
if root is not None:
|
|
78
|
+
self._fragments = {
|
|
79
|
+
"mcp-elicitation#" + fragment: root.evolve(
|
|
80
|
+
schema={"$ref": root.schema["$id"] + "#/$defs/" + fragment}
|
|
81
|
+
)
|
|
82
|
+
for fragment in ("request", "result")
|
|
83
|
+
}
|
|
75
84
|
|
|
76
85
|
def validate(self, kind: str, value: Any) -> Any:
|
|
77
86
|
if kind == "form-answer":
|
|
@@ -82,11 +91,7 @@ class Validator:
|
|
|
82
91
|
).validate(value["value"])
|
|
83
92
|
return value
|
|
84
93
|
if kind in ("mcp-elicitation#request", "mcp-elicitation#result"):
|
|
85
|
-
|
|
86
|
-
root = self.validators[name + ".schema.json"]
|
|
87
|
-
body = root.evolve(
|
|
88
|
-
schema={"$ref": root.schema["$id"] + "#/$defs/" + fragment}
|
|
89
|
-
)
|
|
94
|
+
body = self._fragments[kind]
|
|
90
95
|
if not body.is_valid(value):
|
|
91
96
|
raise ProtocolError("Canonical schema rejected " + kind)
|
|
92
97
|
return value
|
|
@@ -106,8 +111,10 @@ class Validator:
|
|
|
106
111
|
codec = getattr(generated, "parse_" + kind.replace("-", "_"), None)
|
|
107
112
|
if codec is None:
|
|
108
113
|
raise ProtocolError("Generated codec unavailable: " + kind)
|
|
109
|
-
|
|
110
|
-
|
|
114
|
+
error = next(
|
|
115
|
+
self.validators[kind + ".schema.json"].iter_errors(value), None
|
|
116
|
+
)
|
|
117
|
+
if error is not None:
|
|
111
118
|
raise ProtocolError(
|
|
112
119
|
"Canonical schema rejected "
|
|
113
120
|
+ kind
|
{agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol.egg-info/SOURCES.txt
RENAMED
|
@@ -12,6 +12,7 @@ src/agenthooksprotocol/_host_metadata.py
|
|
|
12
12
|
src/agenthooksprotocol/_interop_content.py
|
|
13
13
|
src/agenthooksprotocol/_interop_hooks.py
|
|
14
14
|
src/agenthooksprotocol/_outcomes.py
|
|
15
|
+
src/agenthooksprotocol/_validator.py
|
|
15
16
|
src/agenthooksprotocol/ahp-codegen.lock.json
|
|
16
17
|
src/agenthooksprotocol/attachment.py
|
|
17
18
|
src/agenthooksprotocol/auth.py
|
|
@@ -121,4 +122,5 @@ tests/test_structural_facade.py
|
|
|
121
122
|
tests/test_subscribes.py
|
|
122
123
|
tests/test_typed_capabilities.py
|
|
123
124
|
tests/test_typed_contract_regressions.py
|
|
124
|
-
tests/test_upload_planning.py
|
|
125
|
+
tests/test_upload_planning.py
|
|
126
|
+
tests/test_validator_optimization.py
|