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.
Files changed (128) hide show
  1. {agenthooksprotocol-0.7.0/src/agenthooksprotocol.egg-info → agenthooksprotocol-0.7.1}/PKG-INFO +1 -1
  2. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/pyproject.toml +1 -1
  3. agenthooksprotocol-0.7.1/src/agenthooksprotocol/_validator.py +342 -0
  4. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/runtime.py +17 -10
  5. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1/src/agenthooksprotocol.egg-info}/PKG-INFO +1 -1
  6. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol.egg-info/SOURCES.txt +3 -1
  7. agenthooksprotocol-0.7.1/tests/test_validator_optimization.py +1319 -0
  8. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/LICENSE +0 -0
  9. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/README.md +0 -0
  10. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/setup.cfg +0 -0
  11. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/__init__.py +0 -0
  12. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/_boundaries.py +0 -0
  13. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/_content.py +0 -0
  14. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/_diagnostics.py +0 -0
  15. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/_elicitation.py +0 -0
  16. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/_grants.py +0 -0
  17. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/_hooks.py +0 -0
  18. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/_host_metadata.py +0 -0
  19. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/_interop_content.py +0 -0
  20. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/_interop_hooks.py +0 -0
  21. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/_models/__init__.py +0 -0
  22. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/_models/capability.py +0 -0
  23. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/_models/content.py +0 -0
  24. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/_models/effect.py +0 -0
  25. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/_models/event.py +0 -0
  26. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/_models/registration.py +0 -0
  27. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/_models/subscription.py +0 -0
  28. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/_models/tool.py +0 -0
  29. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/_models/transport.py +0 -0
  30. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/_outcomes.py +0 -0
  31. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/ahp-codegen.lock.json +0 -0
  32. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/attachment.py +0 -0
  33. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/auth.py +0 -0
  34. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/candidate.py +0 -0
  35. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/capability.py +0 -0
  36. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/catalogue.py +0 -0
  37. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/codec.py +0 -0
  38. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/compaction.py +0 -0
  39. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/content.py +0 -0
  40. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/contract.py +0 -0
  41. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/diagnostics.py +0 -0
  42. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/effect.py +0 -0
  43. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/elicitation.py +0 -0
  44. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/event.py +0 -0
  45. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/generated.py +0 -0
  46. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/integrations/__init__.py +0 -0
  47. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/integrations/pydantic.py +0 -0
  48. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/interop.py +0 -0
  49. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/lifecycle.py +0 -0
  50. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/lifecycle_client.py +0 -0
  51. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/lifecycle_server.py +0 -0
  52. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/lineage.py +0 -0
  53. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/observation_chain.py +0 -0
  54. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/path.py +0 -0
  55. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/permission.py +0 -0
  56. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/py.typed +0 -0
  57. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/registration.py +0 -0
  58. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/response.py +0 -0
  59. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/schemas.json +0 -0
  60. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/server/__init__.py +0 -0
  61. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/server/_admission.py +0 -0
  62. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/server/_framing.py +0 -0
  63. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/server/_handlers.py +0 -0
  64. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/server/_json.py +0 -0
  65. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/server/_slots.py +0 -0
  66. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/server/asgi.py +0 -0
  67. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/server/attachments.py +0 -0
  68. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/server/hooks.py +0 -0
  69. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/server/stdio.py +0 -0
  70. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/state.py +0 -0
  71. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/subscription.py +0 -0
  72. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/tool.py +0 -0
  73. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/transport.py +0 -0
  74. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/transports/__init__.py +0 -0
  75. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/transports/http.py +0 -0
  76. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/transports/stdio.py +0 -0
  77. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol/wire.py +0 -0
  78. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol.egg-info/dependency_links.txt +0 -0
  79. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol.egg-info/requires.txt +0 -0
  80. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/src/agenthooksprotocol.egg-info/top_level.txt +0 -0
  81. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/tests/test_attachments.py +0 -0
  82. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/tests/test_catalogue.py +0 -0
  83. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/tests/test_codec.py +0 -0
  84. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/tests/test_compaction.py +0 -0
  85. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/tests/test_compaction_wire.py +0 -0
  86. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/tests/test_consumer_outcomes.py +0 -0
  87. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/tests/test_content.py +0 -0
  88. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/tests/test_content_lifetime.py +0 -0
  89. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/tests/test_content_receipts.py +0 -0
  90. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/tests/test_correlated_core.py +0 -0
  91. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/tests/test_declared_contracts.py +0 -0
  92. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/tests/test_elicitation_adapter.py +0 -0
  93. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/tests/test_event_types.py +0 -0
  94. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/tests/test_fixture_public.py +0 -0
  95. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/tests/test_handwritten_surface.py +0 -0
  96. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/tests/test_harness_identity.py +0 -0
  97. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/tests/test_host_metadata.py +0 -0
  98. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/tests/test_inline_messages.py +0 -0
  99. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/tests/test_integration.py +0 -0
  100. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/tests/test_integration_edges.py +0 -0
  101. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/tests/test_interop.py +0 -0
  102. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/tests/test_lifecycle.py +0 -0
  103. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/tests/test_lifecycle_auth.py +0 -0
  104. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/tests/test_observation.py +0 -0
  105. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/tests/test_observation_chain_transport.py +0 -0
  106. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/tests/test_owned_content.py +0 -0
  107. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/tests/test_owned_content_hooks.py +0 -0
  108. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/tests/test_package.py +0 -0
  109. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/tests/test_public_content.py +0 -0
  110. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/tests/test_public_contract_surface.py +0 -0
  111. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/tests/test_public_examples.py +0 -0
  112. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/tests/test_public_hooks.py +0 -0
  113. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/tests/test_public_server.py +0 -0
  114. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/tests/test_registration_auth.py +0 -0
  115. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/tests/test_safety.py +0 -0
  116. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/tests/test_schema_corrections.py +0 -0
  117. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/tests/test_sdk_unification.py +0 -0
  118. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/tests/test_server_catch_all.py +0 -0
  119. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/tests/test_server_error_codes.py +0 -0
  120. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/tests/test_session_manifest.py +0 -0
  121. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/tests/test_shared_materialization.py +0 -0
  122. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/tests/test_stdio_env.py +0 -0
  123. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/tests/test_stdio_transport.py +0 -0
  124. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/tests/test_structural_facade.py +0 -0
  125. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/tests/test_subscribes.py +0 -0
  126. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/tests/test_typed_capabilities.py +0 -0
  127. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/tests/test_typed_contract_regressions.py +0 -0
  128. {agenthooksprotocol-0.7.0 → agenthooksprotocol-0.7.1}/tests/test_upload_planning.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agenthooksprotocol
3
- Version: 0.7.0
3
+ Version: 0.7.1
4
4
  Summary: Official non-normative Python SDK for the Agent Hooks Protocol
5
5
  Author: Agent Hooks Protocol maintainers
6
6
  License:
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "agenthooksprotocol"
7
- version = "0.7.0"
7
+ version = "0.7.1"
8
8
  description = "Official non-normative Python SDK for the Agent Hooks Protocol"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -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: Draft202012Validator(
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
- name, fragment = kind.split("#")
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
- if not self.validators[kind + ".schema.json"].is_valid(value):
110
- error = next(self.validators[kind + ".schema.json"].iter_errors(value))
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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agenthooksprotocol
3
- Version: 0.7.0
3
+ Version: 0.7.1
4
4
  Summary: Official non-normative Python SDK for the Agent Hooks Protocol
5
5
  Author: Agent Hooks Protocol maintainers
6
6
  License:
@@ -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