schemarouter 0.3.0__tar.gz → 0.4.0__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 (170) hide show
  1. {schemarouter-0.3.0 → schemarouter-0.4.0}/.github/workflows/ci.yml +5 -4
  2. {schemarouter-0.3.0 → schemarouter-0.4.0}/CHANGELOG.md +55 -0
  3. {schemarouter-0.3.0 → schemarouter-0.4.0}/PKG-INFO +120 -7
  4. {schemarouter-0.3.0 → schemarouter-0.4.0}/README.ko.md +116 -7
  5. {schemarouter-0.3.0 → schemarouter-0.4.0}/README.md +117 -6
  6. {schemarouter-0.3.0 → schemarouter-0.4.0}/SECURITY.md +3 -2
  7. {schemarouter-0.3.0 → schemarouter-0.4.0}/docs/architecture.md +83 -38
  8. {schemarouter-0.3.0 → schemarouter-0.4.0}/docs/changelog.md +44 -0
  9. {schemarouter-0.3.0 → schemarouter-0.4.0}/docs/compatibility.md +14 -6
  10. schemarouter-0.4.0/docs/concepts/decision-backends.md +219 -0
  11. {schemarouter-0.3.0 → schemarouter-0.4.0}/docs/concepts/registry.md +35 -2
  12. {schemarouter-0.3.0 → schemarouter-0.4.0}/docs/framework-maturity.md +24 -14
  13. schemarouter-0.4.0/docs/guides/candidate-indexing.md +63 -0
  14. {schemarouter-0.3.0 → schemarouter-0.4.0}/docs/guides/decision-benchmark.md +42 -0
  15. schemarouter-0.4.0/docs/guides/execution-hooks.md +120 -0
  16. schemarouter-0.4.0/docs/guides/field-projection.md +92 -0
  17. {schemarouter-0.3.0 → schemarouter-0.4.0}/docs/guides/openapi-compatibility.md +8 -2
  18. schemarouter-0.4.0/docs/guides/openapi.md +149 -0
  19. schemarouter-0.4.0/docs/guides/run-traces.md +119 -0
  20. {schemarouter-0.3.0 → schemarouter-0.4.0}/docs/guides/runtime.md +21 -0
  21. {schemarouter-0.3.0 → schemarouter-0.4.0}/docs/integrations/langchain.md +2 -0
  22. schemarouter-0.4.0/docs/integrations/langgraph.md +139 -0
  23. schemarouter-0.4.0/docs/integrations/ollama.md +94 -0
  24. {schemarouter-0.3.0 → schemarouter-0.4.0}/docs/reference/planning.md +4 -0
  25. schemarouter-0.4.0/docs/reference/runtime.md +58 -0
  26. schemarouter-0.4.0/docs/releases/0.4.0.md +121 -0
  27. {schemarouter-0.3.0 → schemarouter-0.4.0}/docs/security/threat-model.md +27 -1
  28. schemarouter-0.4.0/examples/langgraph_quickstart.py +45 -0
  29. {schemarouter-0.3.0 → schemarouter-0.4.0}/mkdocs.yml +9 -0
  30. {schemarouter-0.3.0 → schemarouter-0.4.0}/pyproject.toml +4 -1
  31. schemarouter-0.4.0/scripts/benchmark_candidate_index.py +119 -0
  32. {schemarouter-0.3.0 → schemarouter-0.4.0}/scripts/benchmark_decision_routing.py +84 -5
  33. {schemarouter-0.3.0 → schemarouter-0.4.0}/src/schemarouter/__init__.py +28 -1
  34. {schemarouter-0.3.0 → schemarouter-0.4.0}/src/schemarouter/adapters/base.py +4 -0
  35. {schemarouter-0.3.0 → schemarouter-0.4.0}/src/schemarouter/adapters/openapi.py +146 -23
  36. {schemarouter-0.3.0 → schemarouter-0.4.0}/src/schemarouter/decision_policy.py +10 -1
  37. schemarouter-0.4.0/src/schemarouter/decisions.py +371 -0
  38. {schemarouter-0.3.0 → schemarouter-0.4.0}/src/schemarouter/errors.py +8 -0
  39. {schemarouter-0.3.0 → schemarouter-0.4.0}/src/schemarouter/executor.py +137 -23
  40. schemarouter-0.4.0/src/schemarouter/hooks.py +42 -0
  41. {schemarouter-0.3.0 → schemarouter-0.4.0}/src/schemarouter/ingestion.py +275 -6
  42. {schemarouter-0.3.0 → schemarouter-0.4.0}/src/schemarouter/integrations/__init__.py +6 -0
  43. schemarouter-0.4.0/src/schemarouter/integrations/langgraph.py +144 -0
  44. schemarouter-0.4.0/src/schemarouter/integrations/ollama.py +251 -0
  45. {schemarouter-0.3.0 → schemarouter-0.4.0}/src/schemarouter/models.py +27 -0
  46. {schemarouter-0.3.0 → schemarouter-0.4.0}/src/schemarouter/openapi_compatibility.py +45 -35
  47. schemarouter-0.4.0/src/schemarouter/planner.py +950 -0
  48. schemarouter-0.4.0/src/schemarouter/registry.py +370 -0
  49. {schemarouter-0.3.0 → schemarouter-0.4.0}/src/schemarouter/runtime.py +55 -18
  50. schemarouter-0.4.0/src/schemarouter/traces.py +359 -0
  51. schemarouter-0.4.0/tests/test_candidate_index.py +222 -0
  52. schemarouter-0.4.0/tests/test_decision_policy.py +47 -0
  53. schemarouter-0.4.0/tests/test_embedding_decision_backend.py +222 -0
  54. schemarouter-0.4.0/tests/test_evidence_sufficiency.py +334 -0
  55. schemarouter-0.4.0/tests/test_execution_hooks.py +493 -0
  56. schemarouter-0.4.0/tests/test_langgraph_integration.py +136 -0
  57. schemarouter-0.4.0/tests/test_nested_projection.py +284 -0
  58. schemarouter-0.4.0/tests/test_ollama_integration.py +316 -0
  59. schemarouter-0.4.0/tests/test_openapi_external_refs.py +540 -0
  60. schemarouter-0.4.0/tests/test_openapi_ref_composition.py +275 -0
  61. {schemarouter-0.3.0 → schemarouter-0.4.0}/tests/test_planner.py +167 -8
  62. {schemarouter-0.3.0 → schemarouter-0.4.0}/tests/test_public_api.py +14 -0
  63. schemarouter-0.4.0/tests/test_run_traces.py +336 -0
  64. schemarouter-0.4.0/tests/test_sqlite_registry.py +217 -0
  65. schemarouter-0.3.0/docs/concepts/decision-backends.md +0 -100
  66. schemarouter-0.3.0/docs/guides/openapi.md +0 -92
  67. schemarouter-0.3.0/docs/reference/runtime.md +0 -21
  68. schemarouter-0.3.0/src/schemarouter/decisions.py +0 -178
  69. schemarouter-0.3.0/src/schemarouter/planner.py +0 -404
  70. schemarouter-0.3.0/src/schemarouter/registry.py +0 -84
  71. schemarouter-0.3.0/tests/test_decision_policy.py +0 -21
  72. {schemarouter-0.3.0 → schemarouter-0.4.0}/.github/dependabot.yml +0 -0
  73. {schemarouter-0.3.0 → schemarouter-0.4.0}/.github/workflows/compatibility.yml +0 -0
  74. {schemarouter-0.3.0 → schemarouter-0.4.0}/.github/workflows/docs.yml +0 -0
  75. {schemarouter-0.3.0 → schemarouter-0.4.0}/.github/workflows/python-preview.yml +0 -0
  76. {schemarouter-0.3.0 → schemarouter-0.4.0}/.github/workflows/release.yml +0 -0
  77. {schemarouter-0.3.0 → schemarouter-0.4.0}/.gitignore +0 -0
  78. {schemarouter-0.3.0 → schemarouter-0.4.0}/CONTRIBUTING.md +0 -0
  79. {schemarouter-0.3.0 → schemarouter-0.4.0}/LICENSE +0 -0
  80. {schemarouter-0.3.0 → schemarouter-0.4.0}/benchmarks/decision-routing-v1.json +0 -0
  81. {schemarouter-0.3.0 → schemarouter-0.4.0}/docs/adapter-authoring.md +0 -0
  82. {schemarouter-0.3.0 → schemarouter-0.4.0}/docs/assets/brand/README.md +0 -0
  83. {schemarouter-0.3.0 → schemarouter-0.4.0}/docs/assets/brand/schemarouter-lockup-dark.svg +0 -0
  84. {schemarouter-0.3.0 → schemarouter-0.4.0}/docs/assets/brand/schemarouter-lockup-light.svg +0 -0
  85. {schemarouter-0.3.0 → schemarouter-0.4.0}/docs/assets/brand/schemarouter-mark-dark.svg +0 -0
  86. {schemarouter-0.3.0 → schemarouter-0.4.0}/docs/assets/brand/schemarouter-mark-light.svg +0 -0
  87. {schemarouter-0.3.0 → schemarouter-0.4.0}/docs/assets/brand/schemarouter-social-preview.svg +0 -0
  88. {schemarouter-0.3.0 → schemarouter-0.4.0}/docs/concepts/adapters.md +0 -0
  89. {schemarouter-0.3.0 → schemarouter-0.4.0}/docs/concepts/execution.md +0 -0
  90. {schemarouter-0.3.0 → schemarouter-0.4.0}/docs/concepts/planning.md +0 -0
  91. {schemarouter-0.3.0 → schemarouter-0.4.0}/docs/concepts/schema-router.md +0 -0
  92. {schemarouter-0.3.0 → schemarouter-0.4.0}/docs/concepts/tool-endpoint.md +0 -0
  93. {schemarouter-0.3.0 → schemarouter-0.4.0}/docs/contributing.md +0 -0
  94. {schemarouter-0.3.0 → schemarouter-0.4.0}/docs/getting-started/ingestion-paths.md +0 -0
  95. {schemarouter-0.3.0 → schemarouter-0.4.0}/docs/getting-started/installation.md +0 -0
  96. {schemarouter-0.3.0 → schemarouter-0.4.0}/docs/getting-started/quickstart.md +0 -0
  97. {schemarouter-0.3.0 → schemarouter-0.4.0}/docs/guides/adapter-plugins.md +0 -0
  98. {schemarouter-0.3.0 → schemarouter-0.4.0}/docs/guides/execution-policy.md +0 -0
  99. {schemarouter-0.3.0 → schemarouter-0.4.0}/docs/guides/html-documentation.md +0 -0
  100. {schemarouter-0.3.0 → schemarouter-0.4.0}/docs/guides/mcp.md +0 -0
  101. {schemarouter-0.3.0 → schemarouter-0.4.0}/docs/guides/model-analyzer.md +0 -0
  102. {schemarouter-0.3.0 → schemarouter-0.4.0}/docs/guides/optimade.md +0 -0
  103. {schemarouter-0.3.0 → schemarouter-0.4.0}/docs/guides/python-tools.md +0 -0
  104. {schemarouter-0.3.0 → schemarouter-0.4.0}/docs/guides/retry.md +0 -0
  105. {schemarouter-0.3.0 → schemarouter-0.4.0}/docs/index.md +0 -0
  106. {schemarouter-0.3.0 → schemarouter-0.4.0}/docs/integrations/jev.md +0 -0
  107. {schemarouter-0.3.0 → schemarouter-0.4.0}/docs/integrations/llamaindex.md +0 -0
  108. {schemarouter-0.3.0 → schemarouter-0.4.0}/docs/integrations/opentelemetry.md +0 -0
  109. {schemarouter-0.3.0 → schemarouter-0.4.0}/docs/license.md +0 -0
  110. {schemarouter-0.3.0 → schemarouter-0.4.0}/docs/project/brand.md +0 -0
  111. {schemarouter-0.3.0 → schemarouter-0.4.0}/docs/recipes/many-tools.md +0 -0
  112. {schemarouter-0.3.0 → schemarouter-0.4.0}/docs/recipes/mcp-agent.md +0 -0
  113. {schemarouter-0.3.0 → schemarouter-0.4.0}/docs/recipes/openapi-agent.md +0 -0
  114. {schemarouter-0.3.0 → schemarouter-0.4.0}/docs/reference/api.md +0 -0
  115. {schemarouter-0.3.0 → schemarouter-0.4.0}/docs/reference/models.md +0 -0
  116. {schemarouter-0.3.0 → schemarouter-0.4.0}/docs/release-checklist.md +0 -0
  117. {schemarouter-0.3.0 → schemarouter-0.4.0}/docs/releases/0.2.0a1.md +0 -0
  118. {schemarouter-0.3.0 → schemarouter-0.4.0}/docs/releases/0.3.0.md +0 -0
  119. {schemarouter-0.3.0 → schemarouter-0.4.0}/docs/releases/0.3.0a1.md +0 -0
  120. {schemarouter-0.3.0 → schemarouter-0.4.0}/docs/stylesheets/brand.css +0 -0
  121. {schemarouter-0.3.0 → schemarouter-0.4.0}/docs/versioning.md +0 -0
  122. {schemarouter-0.3.0 → schemarouter-0.4.0}/examples/langchain_quickstart.py +0 -0
  123. {schemarouter-0.3.0 → schemarouter-0.4.0}/examples/llamaindex_quickstart.py +0 -0
  124. {schemarouter-0.3.0 → schemarouter-0.4.0}/examples/quickstart.py +0 -0
  125. {schemarouter-0.3.0 → schemarouter-0.4.0}/scripts/live_openapi_smoke.py +0 -0
  126. {schemarouter-0.3.0 → schemarouter-0.4.0}/scripts/live_optimade_smoke.py +0 -0
  127. {schemarouter-0.3.0 → schemarouter-0.4.0}/src/schemarouter/_version.py +0 -0
  128. {schemarouter-0.3.0 → schemarouter-0.4.0}/src/schemarouter/adapters/__init__.py +0 -0
  129. {schemarouter-0.3.0 → schemarouter-0.4.0}/src/schemarouter/adapters/mcp.py +0 -0
  130. {schemarouter-0.3.0 → schemarouter-0.4.0}/src/schemarouter/adapters/optimade.py +0 -0
  131. {schemarouter-0.3.0 → schemarouter-0.4.0}/src/schemarouter/adapters/plugins.py +0 -0
  132. {schemarouter-0.3.0 → schemarouter-0.4.0}/src/schemarouter/adapters/python.py +0 -0
  133. {schemarouter-0.3.0 → schemarouter-0.4.0}/src/schemarouter/analyzers/__init__.py +0 -0
  134. {schemarouter-0.3.0 → schemarouter-0.4.0}/src/schemarouter/analyzers/model.py +0 -0
  135. {schemarouter-0.3.0 → schemarouter-0.4.0}/src/schemarouter/integrations/jev.py +0 -0
  136. {schemarouter-0.3.0 → schemarouter-0.4.0}/src/schemarouter/integrations/langchain.py +0 -0
  137. {schemarouter-0.3.0 → schemarouter-0.4.0}/src/schemarouter/integrations/llamaindex.py +0 -0
  138. {schemarouter-0.3.0 → schemarouter-0.4.0}/src/schemarouter/integrations/opentelemetry.py +0 -0
  139. {schemarouter-0.3.0 → schemarouter-0.4.0}/src/schemarouter/policy.py +0 -0
  140. {schemarouter-0.3.0 → schemarouter-0.4.0}/src/schemarouter/proposals.py +0 -0
  141. {schemarouter-0.3.0 → schemarouter-0.4.0}/src/schemarouter/py.typed +0 -0
  142. {schemarouter-0.3.0 → schemarouter-0.4.0}/src/schemarouter/runs.py +0 -0
  143. {schemarouter-0.3.0 → schemarouter-0.4.0}/src/schemarouter/validation.py +0 -0
  144. {schemarouter-0.3.0 → schemarouter-0.4.0}/tests/fixtures/mcp_http_server.py +0 -0
  145. {schemarouter-0.3.0 → schemarouter-0.4.0}/tests/test_adapter_plugins.py +0 -0
  146. {schemarouter-0.3.0 → schemarouter-0.4.0}/tests/test_adapter_registry.py +0 -0
  147. {schemarouter-0.3.0 → schemarouter-0.4.0}/tests/test_adapters.py +0 -0
  148. {schemarouter-0.3.0 → schemarouter-0.4.0}/tests/test_benchmark_corpus.py +0 -0
  149. {schemarouter-0.3.0 → schemarouter-0.4.0}/tests/test_brand_assets.py +0 -0
  150. {schemarouter-0.3.0 → schemarouter-0.4.0}/tests/test_decisions.py +0 -0
  151. {schemarouter-0.3.0 → schemarouter-0.4.0}/tests/test_execution_controls.py +0 -0
  152. {schemarouter-0.3.0 → schemarouter-0.4.0}/tests/test_executor.py +0 -0
  153. {schemarouter-0.3.0 → schemarouter-0.4.0}/tests/test_ingestion.py +0 -0
  154. {schemarouter-0.3.0 → schemarouter-0.4.0}/tests/test_jev_integration.py +0 -0
  155. {schemarouter-0.3.0 → schemarouter-0.4.0}/tests/test_langchain_integration.py +0 -0
  156. {schemarouter-0.3.0 → schemarouter-0.4.0}/tests/test_llamaindex_integration.py +0 -0
  157. {schemarouter-0.3.0 → schemarouter-0.4.0}/tests/test_mcp_integration.py +0 -0
  158. {schemarouter-0.3.0 → schemarouter-0.4.0}/tests/test_mcp_transport.py +0 -0
  159. {schemarouter-0.3.0 → schemarouter-0.4.0}/tests/test_model_analyzer.py +0 -0
  160. {schemarouter-0.3.0 → schemarouter-0.4.0}/tests/test_openapi_compatibility.py +0 -0
  161. {schemarouter-0.3.0 → schemarouter-0.4.0}/tests/test_opentelemetry_integration.py +0 -0
  162. {schemarouter-0.3.0 → schemarouter-0.4.0}/tests/test_optimade_adapter.py +0 -0
  163. {schemarouter-0.3.0 → schemarouter-0.4.0}/tests/test_optimade_security.py +0 -0
  164. {schemarouter-0.3.0 → schemarouter-0.4.0}/tests/test_package_metadata.py +0 -0
  165. {schemarouter-0.3.0 → schemarouter-0.4.0}/tests/test_policy.py +0 -0
  166. {schemarouter-0.3.0 → schemarouter-0.4.0}/tests/test_proposals.py +0 -0
  167. {schemarouter-0.3.0 → schemarouter-0.4.0}/tests/test_python_adapter.py +0 -0
  168. {schemarouter-0.3.0 → schemarouter-0.4.0}/tests/test_registry.py +0 -0
  169. {schemarouter-0.3.0 → schemarouter-0.4.0}/tests/test_release_metadata.py +0 -0
  170. {schemarouter-0.3.0 → schemarouter-0.4.0}/tests/test_runtime_interface.py +0 -0
@@ -52,7 +52,7 @@ jobs:
52
52
  with:
53
53
  python-version: "3.12"
54
54
  - run: python -m pip install --upgrade pip
55
- - run: pip install -e ".[dev,mcp,langchain,llamaindex,jev,otel]"
55
+ - run: pip install -e ".[dev,mcp,langchain,langgraph,llamaindex,jev,otel]"
56
56
  - run: pyright
57
57
 
58
58
  coverage:
@@ -63,7 +63,7 @@ jobs:
63
63
  with:
64
64
  python-version: "3.12"
65
65
  - run: python -m pip install --upgrade pip
66
- - run: pip install -e ".[dev,mcp,langchain,llamaindex,jev,otel]"
66
+ - run: pip install -e ".[dev,mcp,langchain,langgraph,llamaindex,jev,otel]"
67
67
  - run: pytest -q --cov=schemarouter --cov-branch --cov-report=term-missing --cov-report=xml:coverage.xml
68
68
  - uses: actions/upload-artifact@v7
69
69
  with:
@@ -127,9 +127,10 @@ jobs:
127
127
  with:
128
128
  python-version: "3.12"
129
129
  - run: python -m pip install --upgrade pip
130
- - run: pip install -e ".[dev,langchain]"
131
- - run: pytest -q tests/test_langchain_integration.py
130
+ - run: pip install -e ".[dev,langchain,langgraph]"
131
+ - run: pytest -q tests/test_langchain_integration.py tests/test_langgraph_integration.py
132
132
  - run: python examples/langchain_quickstart.py
133
+ - run: python examples/langgraph_quickstart.py
133
134
 
134
135
  llamaindex-integration:
135
136
  runs-on: ubuntu-latest
@@ -7,6 +7,61 @@ The project is pre-1.0 and follows the compatibility rules in
7
7
 
8
8
  ## Unreleased
9
9
 
10
+ ## 0.4.0 - 2026-09-22
11
+
12
+ ### Added
13
+
14
+ - trusted ordered sync/async before/after execution hooks with detached schema/call/result
15
+ snapshots, non-transforming None-only return contracts, post-await schema/binding refresh, and
16
+ fail-closed non-retryable hook errors.
17
+ - explicit opt-in bounded same-origin OpenAPI cross-document `$ref` bundling with schema-header
18
+ credential confinement, redirect/origin enforcement, depth/document/aggregate-byte budgets,
19
+ complete-document JSON/YAML parsing, cycle caching, runtime schema preservation, and fail-closed
20
+ handling for `$id` rebasing and non-JSON-Pointer anchors.
21
+ - exact-recall candidate indexing cached by registry version, with exhaustive-mode parity tests,
22
+ stable-snapshot rebuilds, synthetic scorer-call benchmarking, and no approximate pruning.
23
+ - conservative bounded `evidence_sufficiency` decision surface with deterministic local
24
+ provenance/license/unit/source-type prechecks, provider veto-only semantics, sync/async support,
25
+ abstention/error fallback policy, and no ability to upgrade missing evidence.
26
+ - explicit `FieldSpec.path` nested-object projection with logical field IDs, full raw-output
27
+ validation before extraction, overlap/collision fail-closed validation, planner path-token
28
+ matching, local projection enforcement for nested paths, and detached projected values.
29
+ - bounded `field_selection` decision surface that exposes only declared non-identifier output
30
+ fields, always preserves identifier fields locally, caps selections by the deterministic
31
+ projection width, recomputes evidence from the final field set, and supports sync/async
32
+ deterministic fallback.
33
+ - append-only `SQLiteRunTraceStore` with strict sequence/run/timestamp invariants, corruption
34
+ fail-closed validation, direct `astream_events(..., trace_store=...)` persistence, redaction-
35
+ preserving storage, and non-executing historical replay.
36
+ - transactional `SQLiteRegistry` persistence with monotonic version retention, deterministic tool
37
+ ordering, atomic batch writes, JSON-only storage, corruption/key-mismatch fail-closed validation,
38
+ and no persistence of trusted invokers or credentials.
39
+ - local Ollama bounded-decision backend using structured JSON Schema output, local option-ID
40
+ revalidation, sync/async HTTP paths, redirect rejection, token metadata capture, adversarial
41
+ mock-transport tests, and shared benchmark-harness support.
42
+ - provider-neutral `EmbeddingDecisionBackend` with sync/async embedding callables, cosine ranking,
43
+ bounded top-k selection, similarity/margin abstention, malformed-vector fail-closed validation,
44
+ and common benchmark-harness support.
45
+ - OpenAPI planner fidelity for chained local references, local Path Item references, safe
46
+ same-document URI-reference normalization during URL ingestion, and object property/required
47
+ discovery through `allOf` while preserving runtime schema validation.
48
+ - native LangGraph `StateGraph` integration through `schemarouter[langgraph]`, with sync/async
49
+ execution, checkpoint-friendly result serialization, custom state-to-request adaptation, contract
50
+ tests, and a runnable example.
51
+
52
+ ### Changed
53
+
54
+ - promoted the 0.4 development line to the non-prerelease `0.4.0` release after the persistence,
55
+ bounded-decision, OpenAPI, LangGraph, and execution-hook surfaces passed the required release gates.
56
+
57
+ ### Compatibility
58
+
59
+ - no intentional public API removals are introduced relative to `0.3.0`;
60
+ - new operational surfaces remain opt-in where they can affect persistence, model-assisted decisions,
61
+ cross-document network access, or execution interception;
62
+ - the project remains pre-1.0, so later 0.x minor releases may still include deliberate documented
63
+ compatibility changes.
64
+
10
65
  ## 0.3.0 - 2026-09-22
11
66
 
12
67
  ### Changed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: schemarouter
3
- Version: 0.3.0
3
+ Version: 0.4.0
4
4
  Summary: Schema-aware planning and execution layer for LLM tool ecosystems
5
5
  Project-URL: Homepage, https://github.com/JDeun/SchemaRouter
6
6
  Project-URL: Repository, https://github.com/JDeun/SchemaRouter
@@ -44,6 +44,8 @@ Provides-Extra: jev
44
44
  Requires-Dist: typesafe-sdk<1,>=0.7; extra == 'jev'
45
45
  Provides-Extra: langchain
46
46
  Requires-Dist: langchain-core<2,>=1.6; extra == 'langchain'
47
+ Provides-Extra: langgraph
48
+ Requires-Dist: langgraph<2,>=1.2; extra == 'langgraph'
47
49
  Provides-Extra: llamaindex
48
50
  Requires-Dist: llama-index-core<1,>=0.14; extra == 'llamaindex'
49
51
  Provides-Extra: mcp
@@ -90,7 +92,7 @@ SchemaRouter is intentionally narrower than LangChain or LangGraph. It is design
90
92
  **tool-schema boundary** between an agent and structured capability sources such as OpenAPI, MCP,
91
93
  OPTIMADE, Python callables, and third-party adapter protocols.
92
94
 
93
- > Status: **0.3.0 is the current public non-prerelease release**. Install it from PyPI with
95
+ > Status: **0.4.0 is the current public non-prerelease release**. Install it from PyPI with
94
96
  > `pip install schemarouter`. SchemaRouter remains pre-1.0, so deliberate compatibility changes
95
97
  > may still occur in later 0.x minor releases under the documented versioning policy.
96
98
 
@@ -164,6 +166,17 @@ router = await SchemaRouter.from_url(
164
166
  )
165
167
  ```
166
168
 
169
+ Cross-document OpenAPI `$ref` fetching stays off by default. Trusted callers can opt into bounded
170
+ same-origin resolution:
171
+
172
+ ```python
173
+ router = await SchemaRouter.from_url(
174
+ "https://api.example.com/openapi.json",
175
+ kind="openapi",
176
+ openapi_external_refs=True,
177
+ )
178
+ ```
179
+
167
180
  ### OPTIMADE
168
181
 
169
182
  ```python
@@ -218,18 +231,28 @@ evidence-grounded proposal and then requires explicit approval.
218
231
  executable calls.
219
232
  - **Runtime JSON Schema validation** — validate arguments before invocation and raw output before
220
233
  projection.
234
+ - **Bounded nested projection** — declared logical fields may map to explicit nested object paths
235
+ without allowing model-produced JSONPath or undeclared field traversal.
221
236
  - **Schema and binding drift detection** — stale plans and stale transports fail closed.
222
237
  - **Local execution authority** — remote metadata and model output cannot grant mutation or
223
238
  destructive permissions.
224
239
  - **Credential separation** — schema-fetch credentials and runtime credentials stay in different
225
240
  channels; authenticated MCP keeps secrets in the trusted transport boundary.
241
+ - **Bounded OpenAPI external refs** — cross-document `$ref` loading is explicit opt-in, confined to
242
+ the entry-document origin, and bounded by redirect/depth/document/byte limits.
226
243
  - **Read-only retries by default** — contract violations are never retried.
227
244
  - **Per-call approval and execution budgets** — trusted local callbacks and deterministic call,
228
245
  attempt, remote, time, quota, and cost-unit limits fail closed.
246
+ - **Trusted execution hooks** — ordered sync/async before/after hooks receive detached snapshots,
247
+ cannot transform calls/results, and fail closed without turning hook failures into tool retries.
229
248
  - **OpenAPI compatibility reporting** — partial/unsupported constructs are machine-readable instead
230
249
  of silently reinterpreted.
231
250
  - **Redacted runtime events by default** — payload tracing is opt-in.
232
- - **Pluggable registry** — custom registries can implement the public `ToolRegistry` protocol.
251
+ - **Replayable persistent traces** — `SQLiteRunTraceStore` can persist validated event streams and
252
+ replay them later without re-running planners, network calls, or tools.
253
+ - **Persistent/pluggable registry** — use the built-in transactional `SQLiteRegistry` or inject a
254
+ custom implementation of the public `ToolRegistry` protocol. Persistent catalog state never
255
+ serializes trusted invokers or credentials.
233
256
  - **Pluggable source adapters** — `AdapterRegistry` lets structured protocols compile into the same
234
257
  `ToolSpec` / `EndpointSpec` execution model; installed entry-point plugins require an explicit
235
258
  allowlist before import.
@@ -250,6 +273,21 @@ tools = to_langchain_tools(router)
250
273
 
251
274
  Execution still flows through SchemaRouter's policy, fingerprint, input, and output validation.
252
275
 
276
+ ## With LangGraph
277
+
278
+ ```bash
279
+ pip install "schemarouter[langgraph]"
280
+ ```
281
+
282
+ ```python
283
+ from schemarouter.integrations import to_langgraph_node
284
+
285
+ builder.add_node("schema_router", to_langgraph_node(router))
286
+ ```
287
+
288
+ The node supports sync/async `StateGraph` execution and returns checkpoint-friendly partial state
289
+ updates while SchemaRouter retains planning, policy, and validated execution authority.
290
+
253
291
  ## With LlamaIndex
254
292
 
255
293
  Install the packaged LlamaIndex bridge:
@@ -269,8 +307,8 @@ endpoint execution.
269
307
 
270
308
  ## Experimental bounded decisions
271
309
 
272
- SchemaRouter includes an optional bounded `DecisionBackend` for tool/endpoint selection. It is
273
- **off by default** and cannot invent executable schema members.
310
+ SchemaRouter includes an optional bounded `DecisionBackend` for tool/endpoint and output-field
311
+ selection. It is **off by default** and cannot invent executable schema members.
274
312
 
275
313
  ```python
276
314
  from schemarouter import DecisionPolicy, SchemaPlanner
@@ -287,6 +325,23 @@ planner = SchemaPlanner(
287
325
  )
288
326
  ```
289
327
 
328
+ A provider-neutral local embedding backend is also available without adding an embedding library to
329
+ SchemaRouter's dependencies:
330
+
331
+ ```python
332
+ from schemarouter import EmbeddingDecisionBackend
333
+
334
+ backend = EmbeddingDecisionBackend(
335
+ embed_batch,
336
+ min_similarity=0.35,
337
+ min_margin=0.05,
338
+ )
339
+ ```
340
+
341
+ The callable can wrap a local SentenceTransformers/FastEmbed-style encoder or an application-owned
342
+ embedding service. SchemaRouter computes cosine ranking locally and can abstain on weak or ambiguous
343
+ matches.
344
+
290
345
  Jev / TypeSafe System One is optional:
291
346
 
292
347
  ```bash
@@ -298,6 +353,44 @@ The provider receives only bounded decision inputs. Unknown option IDs fail clos
298
353
  valid choices can abstain, and deterministic fallback remains available. Jev is never enabled just
299
354
  because the package or an API key exists.
300
355
 
356
+ A local Ollama model can also serve as a bounded decision backend without an additional Python SDK:
357
+
358
+ ```python
359
+ from schemarouter.integrations import OllamaDecisionBackend
360
+
361
+ backend = OllamaDecisionBackend("your-installed-model")
362
+ ```
363
+
364
+ Ollama structured output constrains the finite option IDs, and SchemaRouter revalidates the result
365
+ locally. No local model is enabled automatically.
366
+
367
+ Bounded field selection is independently opt-in:
368
+
369
+ ```python
370
+ policy = DecisionPolicy(
371
+ enabled=True,
372
+ field_selection=True,
373
+ fallback="deterministic",
374
+ )
375
+ ```
376
+
377
+ Only declared non-identifier fields are offered to the backend. Identifier fields are always
378
+ preserved locally, and invalid/abstaining provider output falls back to deterministic projection.
379
+
380
+ Evidence sufficiency is independently opt-in as a conservative gate:
381
+
382
+ ```python
383
+ policy = DecisionPolicy(
384
+ enabled=True,
385
+ evidence_sufficiency=True,
386
+ fallback="deterministic",
387
+ )
388
+ ```
389
+
390
+ Requested provenance/license/unit/source-type requirements must first be satisfied by local schema
391
+ metadata. The backend then receives only `evidence:sufficient` / `evidence:insufficient` and can
392
+ veto a locally sufficient call, but it cannot upgrade missing evidence or grant execution authority.
393
+
301
394
  ## Decision benchmark
302
395
 
303
396
  Run the deterministic baseline:
@@ -306,16 +399,31 @@ Run the deterministic baseline:
306
399
  python scripts/benchmark_decision_routing.py
307
400
  ```
308
401
 
402
+ Compare an embedding backend through a local callable:
403
+
404
+ ```bash
405
+ python scripts/benchmark_decision_routing.py \
406
+ --corpus benchmarks/decision-routing-v1.json \
407
+ --embedding-callable my_embeddings:embed_batch
408
+ ```
409
+
309
410
  Compare Jev when credentials are available:
310
411
 
311
412
  ```bash
312
413
  TYPESAFE_API_KEY="..." python scripts/benchmark_decision_routing.py --jev
313
414
  ```
314
415
 
416
+ Compare an installed local Ollama model:
417
+
418
+ ```bash
419
+ python scripts/benchmark_decision_routing.py --ollama-model your-installed-model
420
+ ```
421
+
315
422
  The harness includes a checked-in 144-case multilingual/adversarial corpus and reports routing
316
423
  accuracy, invalid-plan rate, abstentions/fallbacks, category accuracy, p50/p95 latency, token usage,
317
424
  errors, and optional cost estimates. A provider-neutral `ModelQueryAnalyzer` callable can also be
318
- supplied with `--model-callable module:function`.
425
+ supplied with `--model-callable module:function`; embedding encoders use
426
+ `--embedding-callable module:function`.
319
427
 
320
428
  ```bash
321
429
  python scripts/benchmark_decision_routing.py \
@@ -337,10 +445,15 @@ Full documentation is organized as a framework manual rather than embedded in th
337
445
  - [LangChain integration](https://jdeun.github.io/SchemaRouter/integrations/langchain/)
338
446
  - [LlamaIndex integration](https://jdeun.github.io/SchemaRouter/integrations/llamaindex/)
339
447
  - [Jev / TypeSafe integration](https://jdeun.github.io/SchemaRouter/integrations/jev/)
448
+ - [Ollama decision backend](https://jdeun.github.io/SchemaRouter/integrations/ollama/)
340
449
  - [OpenTelemetry integration](https://jdeun.github.io/SchemaRouter/integrations/opentelemetry/)
341
450
  - [Third-party adapter plugins](https://jdeun.github.io/SchemaRouter/guides/adapter-plugins/)
342
451
  - [Decision backends](https://jdeun.github.io/SchemaRouter/concepts/decision-backends/)
343
452
  - [Decision benchmark](https://jdeun.github.io/SchemaRouter/guides/decision-benchmark/)
453
+ - [Persistent run traces](https://jdeun.github.io/SchemaRouter/guides/run-traces/)
454
+ - [Trusted execution hooks](https://jdeun.github.io/SchemaRouter/guides/execution-hooks/)
455
+ - [Field projection](https://jdeun.github.io/SchemaRouter/guides/field-projection/)
456
+ - [Candidate indexing](https://jdeun.github.io/SchemaRouter/guides/candidate-indexing/)
344
457
  - [API reference](https://jdeun.github.io/SchemaRouter/reference/api/)
345
458
  - [Architecture](https://jdeun.github.io/SchemaRouter/architecture/)
346
459
  - [Security](https://github.com/JDeun/SchemaRouter/blob/main/SECURITY.md)
@@ -365,7 +478,7 @@ python examples/quickstart.py
365
478
  python scripts/benchmark_decision_routing.py
366
479
 
367
480
  # Type-check the complete packaged surface, including optional integrations.
368
- pip install -e ".[dev,mcp,langchain,llamaindex,jev,otel]"
481
+ pip install -e ".[dev,mcp,langchain,langgraph,llamaindex,jev,otel]"
369
482
  pyright
370
483
  pytest -q --cov=schemarouter --cov-branch --cov-report=term-missing
371
484
  ```
@@ -35,7 +35,7 @@ SchemaRouter는 LangChain이나 LangGraph를 대체하는 범용 에이전트
35
35
  OpenAPI, MCP, OPTIMADE, Python callable 및 제3자 어댑터와 에이전트 사이의
36
36
  **tool-schema boundary**를 담당하도록 설계되었습니다.
37
37
 
38
- > 현재 공개 non-prerelease 릴리스는 **0.3.0**입니다. PyPI에서
38
+ > 현재 공개 non-prerelease 릴리스는 **0.4.0**입니다. PyPI에서
39
39
  > `pip install schemarouter`로 설치할 수 있습니다. SchemaRouter는 아직 pre-1.0이므로
40
40
  > 이후 0.x minor 릴리스에서는 문서화된 버전 정책에 따라 의도적인 호환성 변경이 있을 수 있습니다.
41
41
 
@@ -109,6 +109,17 @@ router = await SchemaRouter.from_url(
109
109
  )
110
110
  ```
111
111
 
112
+ OpenAPI cross-document `$ref` fetch는 기본적으로 꺼져 있습니다. 신뢰된 호출자가 명시적으로
113
+ same-origin bounded resolution을 켤 수 있습니다.
114
+
115
+ ```python
116
+ router = await SchemaRouter.from_url(
117
+ "https://api.example.com/openapi.json",
118
+ kind="openapi",
119
+ openapi_external_refs=True,
120
+ )
121
+ ```
122
+
112
123
  ### OPTIMADE
113
124
 
114
125
  ```python
@@ -163,20 +174,27 @@ proposal로 변환한 뒤 명시적인 승인을 받아야 합니다.
163
174
  가능한 호출이 될 수 없습니다.
164
175
  - **Runtime JSON Schema validation** — 호출 전 argument와 projection 전 raw output을
165
176
  검증합니다.
177
+ - **Bounded nested projection** — 선언된 logical field만 명시적 nested object path에
178
+ 매핑할 수 있으며 모델이 임의 JSONPath나 미선언 경로를 만들 수 없습니다.
166
179
  - **Schema / binding drift detection** — 오래된 plan이나 transport binding은 fail closed로
167
180
  차단합니다.
168
181
  - **Local execution authority** — 원격 metadata나 모델 출력이 mutation/destructive 권한을
169
182
  부여할 수 없습니다.
170
183
  - **Credential separation** — schema fetch credential과 runtime credential을 분리하며,
171
184
  인증 MCP secret은 trusted transport 경계 안에만 둡니다.
185
+ - **Bounded OpenAPI external ref** — cross-document `$ref`는 명시적 opt-in일 때만 entry
186
+ document와 같은 origin에서 fetch되며 redirect/depth/document/byte limit을 적용합니다.
172
187
  - **Read-only retry by default** — 계약 위반이나 위험한 호출을 자동 재시도하지 않습니다.
173
188
  - **호출별 승인 및 실행 budget** — trusted local callback과 call/attempt/remote/time/quota/
174
189
  cost-unit 제한을 fail-closed로 적용합니다.
190
+ - **Trusted execution hook** — 순서가 보장되는 sync/async before/after hook은 detached snapshot만
191
+ 받고 call/result를 변환하지 못하며, hook 실패를 tool retry로 오인하지 않고 fail closed 처리합니다.
175
192
  - **OpenAPI compatibility report** — partial/unsupported construct를 숨기지 않고
176
193
  machine-readable report로 노출합니다.
177
194
  - **Redacted runtime event by default** — payload trace는 명시적으로 opt-in해야 합니다.
178
- - **Pluggable registry** — 커스텀 registry는 공개 `ToolRegistry` protocol을 구현할 수
179
- 있습니다.
195
+ - **Persistent/pluggable registry** — built-in transactional `SQLiteRegistry`를 사용하거나
196
+ 공개 `ToolRegistry` protocol 기반 커스텀 구현을 주입할 수 있습니다. persistent catalog에는
197
+ trusted invoker나 credential을 직렬화하지 않습니다.
180
198
  - **Pluggable adapter** — `AdapterRegistry`를 통해 다양한 structured protocol을 동일한
181
199
  `ToolSpec` / `EndpointSpec` 실행 모델로 변환할 수 있으며, 설치된 entry-point plugin은
182
200
  명시적 allowlist가 있어야 import됩니다.
@@ -198,6 +216,21 @@ tools = to_langchain_tools(router)
198
216
  LangChain 도구로 노출해도 실행은 SchemaRouter의 policy, fingerprint, input/output validation
199
217
  경계를 그대로 통과합니다.
200
218
 
219
+ ## LangGraph와 사용
220
+
221
+ ```bash
222
+ pip install "schemarouter[langgraph]"
223
+ ```
224
+
225
+ ```python
226
+ from schemarouter.integrations import to_langgraph_node
227
+
228
+ builder.add_node("schema_router", to_langgraph_node(router))
229
+ ```
230
+
231
+ 이 노드는 sync/async `StateGraph` 실행을 지원하고 checkpoint에 적합한 partial state update를
232
+ 반환합니다. planning, policy, 검증된 execution authority는 SchemaRouter가 계속 유지합니다.
233
+
201
234
  ## LlamaIndex와 사용
202
235
 
203
236
  LlamaIndex bridge는 패키지 extra로 설치할 수 있습니다.
@@ -217,8 +250,8 @@ validation 및 endpoint execution 경계를 유지합니다.
217
250
 
218
251
  ## 실험적 bounded decision
219
252
 
220
- SchemaRouter는 tool/endpoint 선택을 보조하는 opt-in `DecisionBackend`를 제공합니다.
221
- 기본값은 **OFF**이며 모델이 임의의 실행 가능한 스키마 멤버를 만들 수 없습니다.
253
+ SchemaRouter는 tool/endpoint output field 선택을 보조하는 opt-in `DecisionBackend`를
254
+ 제공합니다. 기본값은 **OFF**이며 모델이 임의의 실행 가능한 스키마 멤버를 만들 수 없습니다.
222
255
 
223
256
  ```python
224
257
  from schemarouter import DecisionPolicy, SchemaPlanner
@@ -235,6 +268,23 @@ planner = SchemaPlanner(
235
268
  )
236
269
  ```
237
270
 
271
+ 특정 provider에 종속되지 않는 local embedding backend도 core dependency 추가 없이 사용할 수
272
+ 있습니다.
273
+
274
+ ```python
275
+ from schemarouter import EmbeddingDecisionBackend
276
+
277
+ backend = EmbeddingDecisionBackend(
278
+ embed_batch,
279
+ min_similarity=0.35,
280
+ min_margin=0.05,
281
+ )
282
+ ```
283
+
284
+ callable에는 local SentenceTransformers/FastEmbed 계열 encoder나 애플리케이션 소유 embedding
285
+ service를 연결할 수 있습니다. cosine ranking은 SchemaRouter가 로컬에서 수행하며 약하거나
286
+ 모호한 선택은 abstain할 수 있습니다.
287
+
238
288
  Jev / TypeSafe System One은 선택형 기능입니다.
239
289
 
240
290
  ```bash
@@ -248,6 +298,45 @@ abstain한 뒤 deterministic fallback으로 돌아갈 수 있습니다.
248
298
 
249
299
  패키지를 설치했거나 API key가 존재한다는 이유만으로 Jev가 자동 활성화되지는 않습니다.
250
300
 
301
+ 로컬 Ollama 모델도 별도 Python SDK 없이 bounded decision backend로 사용할 수 있습니다.
302
+
303
+ ```python
304
+ from schemarouter.integrations import OllamaDecisionBackend
305
+
306
+ backend = OllamaDecisionBackend("your-installed-model")
307
+ ```
308
+
309
+ Ollama structured output으로 유한한 option ID를 제한하고, SchemaRouter가 결과를 다시 로컬
310
+ 검증합니다. 로컬 모델이 존재한다는 이유만으로 자동 활성화되지는 않습니다.
311
+
312
+ bounded field selection도 별도로 opt-in할 수 있습니다.
313
+
314
+ ```python
315
+ policy = DecisionPolicy(
316
+ enabled=True,
317
+ field_selection=True,
318
+ fallback="deterministic",
319
+ )
320
+ ```
321
+
322
+ backend에는 선언된 non-identifier field만 제공되고 identifier field는 항상 로컬에서
323
+ 보존됩니다. invalid output이나 abstain은 deterministic projection으로 fallback됩니다.
324
+
325
+ Evidence sufficiency도 별도로 opt-in할 수 있는 보수적 gate입니다.
326
+
327
+ ```python
328
+ policy = DecisionPolicy(
329
+ enabled=True,
330
+ evidence_sufficiency=True,
331
+ fallback="deterministic",
332
+ )
333
+ ```
334
+
335
+ 요청된 provenance/license/unit/source-type은 먼저 로컬 schema metadata로 충족되어야 합니다.
336
+ 그 뒤 backend는 `evidence:sufficient` / `evidence:insufficient` 두 선택지만 받으며,
337
+ 로컬에서 충분한 call을 veto할 수 있을 뿐 없는 evidence를 만들어내거나 실행 권한을 높일 수
338
+ 없습니다.
339
+
251
340
  ## Decision benchmark
252
341
 
253
342
  기본 deterministic routing을 측정합니다.
@@ -256,16 +345,31 @@ abstain한 뒤 deterministic fallback으로 돌아갈 수 있습니다.
256
345
  python scripts/benchmark_decision_routing.py
257
346
  ```
258
347
 
348
+ local embedding callable도 같은 corpus에서 비교할 수 있습니다.
349
+
350
+ ```bash
351
+ python scripts/benchmark_decision_routing.py \
352
+ --corpus benchmarks/decision-routing-v1.json \
353
+ --embedding-callable my_embeddings:embed_batch
354
+ ```
355
+
259
356
  API key가 있다면 같은 케이스로 Jev도 비교할 수 있습니다.
260
357
 
261
358
  ```bash
262
359
  TYPESAFE_API_KEY="..." python scripts/benchmark_decision_routing.py --jev
263
360
  ```
264
361
 
362
+ 설치된 로컬 Ollama 모델도 같은 corpus로 비교할 수 있습니다.
363
+
364
+ ```bash
365
+ python scripts/benchmark_decision_routing.py --ollama-model your-installed-model
366
+ ```
367
+
265
368
  benchmark에는 144개 multilingual/adversarial 고정 corpus가 포함되며 routing accuracy,
266
369
  invalid-plan rate, abstention/fallback, category accuracy, p50/p95 latency, token usage, error와
267
370
  선택적인 비용 추정치를 기록합니다. `--model-callable module:function`을 이용하면
268
- provider-neutral `ModelQueryAnalyzer`도 같은 harness에서 비교할 수 있습니다.
371
+ provider-neutral `ModelQueryAnalyzer`도 같은 harness에서 비교할 수 있고, embedding
372
+ encoder는 `--embedding-callable module:function`으로 연결할 수 있습니다.
269
373
 
270
374
  ```bash
271
375
  python scripts/benchmark_decision_routing.py \
@@ -287,10 +391,15 @@ python scripts/benchmark_decision_routing.py \
287
391
  - [LangChain integration](https://jdeun.github.io/SchemaRouter/integrations/langchain/)
288
392
  - [LlamaIndex integration](https://jdeun.github.io/SchemaRouter/integrations/llamaindex/)
289
393
  - [Jev / TypeSafe integration](https://jdeun.github.io/SchemaRouter/integrations/jev/)
394
+ - [Ollama decision backend](https://jdeun.github.io/SchemaRouter/integrations/ollama/)
290
395
  - [OpenTelemetry integration](https://jdeun.github.io/SchemaRouter/integrations/opentelemetry/)
291
396
  - [Third-party adapter plugins](https://jdeun.github.io/SchemaRouter/guides/adapter-plugins/)
292
397
  - [Decision backends](https://jdeun.github.io/SchemaRouter/concepts/decision-backends/)
293
398
  - [Decision benchmark](https://jdeun.github.io/SchemaRouter/guides/decision-benchmark/)
399
+ - [Persistent run traces](https://jdeun.github.io/SchemaRouter/guides/run-traces/)
400
+ - [Trusted execution hooks](https://jdeun.github.io/SchemaRouter/guides/execution-hooks/)
401
+ - [Field projection](https://jdeun.github.io/SchemaRouter/guides/field-projection/)
402
+ - [Candidate indexing](https://jdeun.github.io/SchemaRouter/guides/candidate-indexing/)
294
403
  - [API reference](https://jdeun.github.io/SchemaRouter/reference/api/)
295
404
  - [Architecture](https://jdeun.github.io/SchemaRouter/architecture/)
296
405
  - [Security](https://github.com/JDeun/SchemaRouter/blob/main/SECURITY.md)
@@ -315,7 +424,7 @@ python examples/quickstart.py
315
424
  python scripts/benchmark_decision_routing.py
316
425
 
317
426
  # 선택형 통합을 포함한 전체 패키지 surface 타입 검사
318
- pip install -e ".[dev,mcp,langchain,llamaindex,jev,otel]"
427
+ pip install -e ".[dev,mcp,langchain,langgraph,llamaindex,jev,otel]"
319
428
  pyright
320
429
  pytest -q --cov=schemarouter --cov-branch --cov-report=term-missing
321
430
  ```