schemarouter 0.2.0a1__tar.gz → 0.3.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 (152) hide show
  1. schemarouter-0.3.0/.github/workflows/ci.yml +190 -0
  2. schemarouter-0.3.0/.github/workflows/python-preview.yml +30 -0
  3. schemarouter-0.3.0/.github/workflows/release.yml +353 -0
  4. schemarouter-0.3.0/CHANGELOG.md +119 -0
  5. {schemarouter-0.2.0a1 → schemarouter-0.3.0}/CONTRIBUTING.md +16 -9
  6. {schemarouter-0.2.0a1 → schemarouter-0.3.0}/PKG-INFO +125 -12
  7. schemarouter-0.3.0/README.ko.md +364 -0
  8. {schemarouter-0.2.0a1 → schemarouter-0.3.0}/README.md +112 -10
  9. {schemarouter-0.2.0a1 → schemarouter-0.3.0}/SECURITY.md +35 -8
  10. schemarouter-0.3.0/benchmarks/decision-routing-v1.json +1010 -0
  11. {schemarouter-0.2.0a1 → schemarouter-0.3.0}/docs/adapter-authoring.md +20 -0
  12. {schemarouter-0.2.0a1 → schemarouter-0.3.0}/docs/architecture.md +68 -8
  13. schemarouter-0.3.0/docs/changelog.md +119 -0
  14. schemarouter-0.3.0/docs/compatibility.md +112 -0
  15. schemarouter-0.3.0/docs/concepts/decision-backends.md +100 -0
  16. {schemarouter-0.2.0a1 → schemarouter-0.3.0}/docs/contributing.md +16 -9
  17. {schemarouter-0.2.0a1 → schemarouter-0.3.0}/docs/framework-maturity.md +49 -26
  18. schemarouter-0.3.0/docs/getting-started/installation.md +82 -0
  19. schemarouter-0.3.0/docs/guides/adapter-plugins.md +76 -0
  20. schemarouter-0.3.0/docs/guides/decision-benchmark.md +116 -0
  21. schemarouter-0.3.0/docs/guides/execution-policy.md +139 -0
  22. schemarouter-0.3.0/docs/guides/mcp.md +117 -0
  23. schemarouter-0.3.0/docs/guides/openapi-compatibility.md +60 -0
  24. {schemarouter-0.2.0a1 → schemarouter-0.3.0}/docs/guides/openapi.md +10 -0
  25. {schemarouter-0.2.0a1 → schemarouter-0.3.0}/docs/guides/runtime.md +18 -0
  26. {schemarouter-0.2.0a1 → schemarouter-0.3.0}/docs/index.md +20 -8
  27. schemarouter-0.3.0/docs/integrations/jev.md +132 -0
  28. {schemarouter-0.2.0a1 → schemarouter-0.3.0}/docs/integrations/langchain.md +28 -1
  29. schemarouter-0.3.0/docs/integrations/llamaindex.md +57 -0
  30. schemarouter-0.3.0/docs/integrations/opentelemetry.md +67 -0
  31. {schemarouter-0.2.0a1 → schemarouter-0.3.0}/docs/reference/api.md +25 -0
  32. {schemarouter-0.2.0a1 → schemarouter-0.3.0}/docs/reference/runtime.md +4 -0
  33. schemarouter-0.3.0/docs/release-checklist.md +74 -0
  34. {schemarouter-0.2.0a1 → schemarouter-0.3.0}/docs/releases/0.2.0a1.md +2 -2
  35. schemarouter-0.3.0/docs/releases/0.3.0.md +79 -0
  36. schemarouter-0.3.0/docs/releases/0.3.0a1.md +109 -0
  37. {schemarouter-0.2.0a1 → schemarouter-0.3.0}/docs/security/threat-model.md +36 -9
  38. {schemarouter-0.2.0a1 → schemarouter-0.3.0}/docs/versioning.md +29 -2
  39. schemarouter-0.3.0/examples/langchain_quickstart.py +25 -0
  40. schemarouter-0.3.0/examples/llamaindex_quickstart.py +25 -0
  41. {schemarouter-0.2.0a1 → schemarouter-0.3.0}/mkdocs.yml +7 -0
  42. {schemarouter-0.2.0a1 → schemarouter-0.3.0}/pyproject.toml +31 -2
  43. schemarouter-0.3.0/scripts/benchmark_decision_routing.py +548 -0
  44. {schemarouter-0.2.0a1 → schemarouter-0.3.0}/src/schemarouter/__init__.py +54 -2
  45. {schemarouter-0.2.0a1 → schemarouter-0.3.0}/src/schemarouter/adapters/__init__.py +27 -1
  46. {schemarouter-0.2.0a1 → schemarouter-0.3.0}/src/schemarouter/adapters/base.py +1 -0
  47. {schemarouter-0.2.0a1 → schemarouter-0.3.0}/src/schemarouter/adapters/mcp.py +119 -19
  48. {schemarouter-0.2.0a1 → schemarouter-0.3.0}/src/schemarouter/adapters/openapi.py +71 -15
  49. schemarouter-0.3.0/src/schemarouter/adapters/plugins.py +133 -0
  50. {schemarouter-0.2.0a1 → schemarouter-0.3.0}/src/schemarouter/adapters/python.py +3 -2
  51. schemarouter-0.3.0/src/schemarouter/decision_policy.py +28 -0
  52. schemarouter-0.3.0/src/schemarouter/decisions.py +178 -0
  53. {schemarouter-0.2.0a1 → schemarouter-0.3.0}/src/schemarouter/errors.py +8 -0
  54. {schemarouter-0.2.0a1 → schemarouter-0.3.0}/src/schemarouter/executor.py +173 -12
  55. {schemarouter-0.2.0a1 → schemarouter-0.3.0}/src/schemarouter/ingestion.py +14 -6
  56. schemarouter-0.3.0/src/schemarouter/integrations/__init__.py +14 -0
  57. schemarouter-0.3.0/src/schemarouter/integrations/jev.py +227 -0
  58. schemarouter-0.3.0/src/schemarouter/integrations/llamaindex.py +173 -0
  59. schemarouter-0.3.0/src/schemarouter/integrations/opentelemetry.py +179 -0
  60. schemarouter-0.3.0/src/schemarouter/openapi_compatibility.py +423 -0
  61. {schemarouter-0.2.0a1 → schemarouter-0.3.0}/src/schemarouter/planner.py +156 -5
  62. {schemarouter-0.2.0a1 → schemarouter-0.3.0}/src/schemarouter/policy.py +34 -10
  63. {schemarouter-0.2.0a1 → schemarouter-0.3.0}/src/schemarouter/runs.py +32 -1
  64. {schemarouter-0.2.0a1 → schemarouter-0.3.0}/src/schemarouter/runtime.py +61 -11
  65. schemarouter-0.3.0/tests/test_adapter_plugins.py +126 -0
  66. {schemarouter-0.2.0a1 → schemarouter-0.3.0}/tests/test_adapters.py +152 -0
  67. schemarouter-0.3.0/tests/test_benchmark_corpus.py +69 -0
  68. schemarouter-0.3.0/tests/test_decision_policy.py +21 -0
  69. schemarouter-0.3.0/tests/test_decisions.py +114 -0
  70. schemarouter-0.3.0/tests/test_execution_controls.py +355 -0
  71. schemarouter-0.3.0/tests/test_jev_integration.py +216 -0
  72. schemarouter-0.3.0/tests/test_llamaindex_integration.py +81 -0
  73. {schemarouter-0.2.0a1 → schemarouter-0.3.0}/tests/test_mcp_integration.py +3 -0
  74. schemarouter-0.3.0/tests/test_mcp_transport.py +134 -0
  75. schemarouter-0.3.0/tests/test_openapi_compatibility.py +149 -0
  76. schemarouter-0.3.0/tests/test_opentelemetry_integration.py +152 -0
  77. schemarouter-0.3.0/tests/test_package_metadata.py +7 -0
  78. schemarouter-0.3.0/tests/test_planner.py +218 -0
  79. {schemarouter-0.2.0a1 → schemarouter-0.3.0}/tests/test_public_api.py +25 -0
  80. {schemarouter-0.2.0a1 → schemarouter-0.3.0}/tests/test_registry.py +66 -0
  81. schemarouter-0.3.0/tests/test_release_metadata.py +113 -0
  82. schemarouter-0.2.0a1/.github/workflows/ci.yml +0 -90
  83. schemarouter-0.2.0a1/.github/workflows/release.yml +0 -113
  84. schemarouter-0.2.0a1/CHANGELOG.md +0 -45
  85. schemarouter-0.2.0a1/docs/changelog.md +0 -36
  86. schemarouter-0.2.0a1/docs/compatibility.md +0 -48
  87. schemarouter-0.2.0a1/docs/getting-started/installation.md +0 -61
  88. schemarouter-0.2.0a1/docs/guides/execution-policy.md +0 -58
  89. schemarouter-0.2.0a1/docs/guides/mcp.md +0 -62
  90. schemarouter-0.2.0a1/docs/release-checklist.md +0 -49
  91. schemarouter-0.2.0a1/src/schemarouter/integrations/__init__.py +0 -3
  92. schemarouter-0.2.0a1/tests/test_package_metadata.py +0 -5
  93. schemarouter-0.2.0a1/tests/test_planner.py +0 -79
  94. schemarouter-0.2.0a1/tests/test_release_metadata.py +0 -47
  95. {schemarouter-0.2.0a1 → schemarouter-0.3.0}/.github/dependabot.yml +0 -0
  96. {schemarouter-0.2.0a1 → schemarouter-0.3.0}/.github/workflows/compatibility.yml +0 -0
  97. {schemarouter-0.2.0a1 → schemarouter-0.3.0}/.github/workflows/docs.yml +0 -0
  98. {schemarouter-0.2.0a1 → schemarouter-0.3.0}/.gitignore +0 -0
  99. {schemarouter-0.2.0a1 → schemarouter-0.3.0}/LICENSE +0 -0
  100. {schemarouter-0.2.0a1 → schemarouter-0.3.0}/docs/assets/brand/README.md +0 -0
  101. {schemarouter-0.2.0a1 → schemarouter-0.3.0}/docs/assets/brand/schemarouter-lockup-dark.svg +0 -0
  102. {schemarouter-0.2.0a1 → schemarouter-0.3.0}/docs/assets/brand/schemarouter-lockup-light.svg +0 -0
  103. {schemarouter-0.2.0a1 → schemarouter-0.3.0}/docs/assets/brand/schemarouter-mark-dark.svg +0 -0
  104. {schemarouter-0.2.0a1 → schemarouter-0.3.0}/docs/assets/brand/schemarouter-mark-light.svg +0 -0
  105. {schemarouter-0.2.0a1 → schemarouter-0.3.0}/docs/assets/brand/schemarouter-social-preview.svg +0 -0
  106. {schemarouter-0.2.0a1 → schemarouter-0.3.0}/docs/concepts/adapters.md +0 -0
  107. {schemarouter-0.2.0a1 → schemarouter-0.3.0}/docs/concepts/execution.md +0 -0
  108. {schemarouter-0.2.0a1 → schemarouter-0.3.0}/docs/concepts/planning.md +0 -0
  109. {schemarouter-0.2.0a1 → schemarouter-0.3.0}/docs/concepts/registry.md +0 -0
  110. {schemarouter-0.2.0a1 → schemarouter-0.3.0}/docs/concepts/schema-router.md +0 -0
  111. {schemarouter-0.2.0a1 → schemarouter-0.3.0}/docs/concepts/tool-endpoint.md +0 -0
  112. {schemarouter-0.2.0a1 → schemarouter-0.3.0}/docs/getting-started/ingestion-paths.md +0 -0
  113. {schemarouter-0.2.0a1 → schemarouter-0.3.0}/docs/getting-started/quickstart.md +0 -0
  114. {schemarouter-0.2.0a1 → schemarouter-0.3.0}/docs/guides/html-documentation.md +0 -0
  115. {schemarouter-0.2.0a1 → schemarouter-0.3.0}/docs/guides/model-analyzer.md +0 -0
  116. {schemarouter-0.2.0a1 → schemarouter-0.3.0}/docs/guides/optimade.md +0 -0
  117. {schemarouter-0.2.0a1 → schemarouter-0.3.0}/docs/guides/python-tools.md +0 -0
  118. {schemarouter-0.2.0a1 → schemarouter-0.3.0}/docs/guides/retry.md +0 -0
  119. {schemarouter-0.2.0a1 → schemarouter-0.3.0}/docs/license.md +0 -0
  120. {schemarouter-0.2.0a1 → schemarouter-0.3.0}/docs/project/brand.md +0 -0
  121. {schemarouter-0.2.0a1 → schemarouter-0.3.0}/docs/recipes/many-tools.md +0 -0
  122. {schemarouter-0.2.0a1 → schemarouter-0.3.0}/docs/recipes/mcp-agent.md +0 -0
  123. {schemarouter-0.2.0a1 → schemarouter-0.3.0}/docs/recipes/openapi-agent.md +0 -0
  124. {schemarouter-0.2.0a1 → schemarouter-0.3.0}/docs/reference/models.md +0 -0
  125. {schemarouter-0.2.0a1 → schemarouter-0.3.0}/docs/reference/planning.md +0 -0
  126. {schemarouter-0.2.0a1 → schemarouter-0.3.0}/docs/stylesheets/brand.css +0 -0
  127. {schemarouter-0.2.0a1 → schemarouter-0.3.0}/examples/quickstart.py +0 -0
  128. {schemarouter-0.2.0a1 → schemarouter-0.3.0}/scripts/live_openapi_smoke.py +0 -0
  129. {schemarouter-0.2.0a1 → schemarouter-0.3.0}/scripts/live_optimade_smoke.py +0 -0
  130. {schemarouter-0.2.0a1 → schemarouter-0.3.0}/src/schemarouter/_version.py +0 -0
  131. {schemarouter-0.2.0a1 → schemarouter-0.3.0}/src/schemarouter/adapters/optimade.py +0 -0
  132. {schemarouter-0.2.0a1 → schemarouter-0.3.0}/src/schemarouter/analyzers/__init__.py +0 -0
  133. {schemarouter-0.2.0a1 → schemarouter-0.3.0}/src/schemarouter/analyzers/model.py +0 -0
  134. {schemarouter-0.2.0a1 → schemarouter-0.3.0}/src/schemarouter/integrations/langchain.py +0 -0
  135. {schemarouter-0.2.0a1 → schemarouter-0.3.0}/src/schemarouter/models.py +0 -0
  136. {schemarouter-0.2.0a1 → schemarouter-0.3.0}/src/schemarouter/proposals.py +0 -0
  137. {schemarouter-0.2.0a1 → schemarouter-0.3.0}/src/schemarouter/py.typed +0 -0
  138. {schemarouter-0.2.0a1 → schemarouter-0.3.0}/src/schemarouter/registry.py +0 -0
  139. {schemarouter-0.2.0a1 → schemarouter-0.3.0}/src/schemarouter/validation.py +0 -0
  140. {schemarouter-0.2.0a1 → schemarouter-0.3.0}/tests/fixtures/mcp_http_server.py +0 -0
  141. {schemarouter-0.2.0a1 → schemarouter-0.3.0}/tests/test_adapter_registry.py +0 -0
  142. {schemarouter-0.2.0a1 → schemarouter-0.3.0}/tests/test_brand_assets.py +0 -0
  143. {schemarouter-0.2.0a1 → schemarouter-0.3.0}/tests/test_executor.py +0 -0
  144. {schemarouter-0.2.0a1 → schemarouter-0.3.0}/tests/test_ingestion.py +0 -0
  145. {schemarouter-0.2.0a1 → schemarouter-0.3.0}/tests/test_langchain_integration.py +0 -0
  146. {schemarouter-0.2.0a1 → schemarouter-0.3.0}/tests/test_model_analyzer.py +0 -0
  147. {schemarouter-0.2.0a1 → schemarouter-0.3.0}/tests/test_optimade_adapter.py +0 -0
  148. {schemarouter-0.2.0a1 → schemarouter-0.3.0}/tests/test_optimade_security.py +0 -0
  149. {schemarouter-0.2.0a1 → schemarouter-0.3.0}/tests/test_policy.py +0 -0
  150. {schemarouter-0.2.0a1 → schemarouter-0.3.0}/tests/test_proposals.py +0 -0
  151. {schemarouter-0.2.0a1 → schemarouter-0.3.0}/tests/test_python_adapter.py +0 -0
  152. {schemarouter-0.2.0a1 → schemarouter-0.3.0}/tests/test_runtime_interface.py +0 -0
@@ -0,0 +1,190 @@
1
+ name: CI
2
+
3
+ on:
4
+ workflow_call:
5
+ push:
6
+ branches: [main]
7
+ pull_request:
8
+
9
+ permissions:
10
+ contents: read
11
+
12
+ concurrency:
13
+ group: ci-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
14
+ cancel-in-progress: true
15
+
16
+ jobs:
17
+ test:
18
+ runs-on: ubuntu-latest
19
+ strategy:
20
+ fail-fast: false
21
+ matrix:
22
+ python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
23
+ steps:
24
+ - uses: actions/checkout@v7
25
+ - uses: actions/setup-python@v7
26
+ with:
27
+ python-version: ${{ matrix.python-version }}
28
+ - run: python -m pip install --upgrade pip
29
+ - run: pip install -e ".[dev]"
30
+ - run: ruff check .
31
+ - run: pytest -q -m "not mcp_integration"
32
+ - run: python examples/quickstart.py
33
+
34
+ windows-smoke:
35
+ runs-on: windows-latest
36
+ steps:
37
+ - uses: actions/checkout@v7
38
+ - uses: actions/setup-python@v7
39
+ with:
40
+ python-version: "3.14"
41
+ - run: python -m pip install --upgrade pip
42
+ - run: pip install -e ".[dev]"
43
+ - run: ruff check .
44
+ - run: pytest -q -m "not mcp_integration"
45
+ - run: python examples/quickstart.py
46
+
47
+ typecheck:
48
+ runs-on: ubuntu-latest
49
+ steps:
50
+ - uses: actions/checkout@v7
51
+ - uses: actions/setup-python@v7
52
+ with:
53
+ python-version: "3.12"
54
+ - run: python -m pip install --upgrade pip
55
+ - run: pip install -e ".[dev,mcp,langchain,llamaindex,jev,otel]"
56
+ - run: pyright
57
+
58
+ coverage:
59
+ runs-on: ubuntu-latest
60
+ steps:
61
+ - uses: actions/checkout@v7
62
+ - uses: actions/setup-python@v7
63
+ with:
64
+ python-version: "3.12"
65
+ - run: python -m pip install --upgrade pip
66
+ - run: pip install -e ".[dev,mcp,langchain,llamaindex,jev,otel]"
67
+ - run: pytest -q --cov=schemarouter --cov-branch --cov-report=term-missing --cov-report=xml:coverage.xml
68
+ - uses: actions/upload-artifact@v7
69
+ with:
70
+ name: coverage-report
71
+ path: coverage.xml
72
+ if-no-files-found: error
73
+ retention-days: 14
74
+
75
+ minimum-dependencies:
76
+ runs-on: ubuntu-latest
77
+ steps:
78
+ - uses: actions/checkout@v7
79
+ - uses: actions/setup-python@v7
80
+ with:
81
+ python-version: "3.10"
82
+ - run: python -m pip install --upgrade pip
83
+ - run: pip install "pytest>=8.2" "pytest-asyncio>=0.23"
84
+ - run: pip install "httpx==0.27.0" "jsonschema==4.23.0" "pydantic==2.8.0" "pyyaml==6.0.0"
85
+ - run: pip install -e . --no-deps
86
+ - run: pytest -q -m "not mcp_integration"
87
+ - run: python examples/quickstart.py
88
+
89
+ package:
90
+ runs-on: ubuntu-latest
91
+ steps:
92
+ - uses: actions/checkout@v7
93
+ - uses: actions/setup-python@v7
94
+ with:
95
+ python-version: "3.12"
96
+ - run: python -m pip install --upgrade pip
97
+ - run: pip install build twine
98
+ - run: python -m build
99
+ - run: twine check dist/*
100
+ - name: Verify license is packaged
101
+ run: |
102
+ python - <<'PY'
103
+ import glob
104
+ import zipfile
105
+
106
+ wheel = glob.glob("dist/*.whl")[0]
107
+ names = zipfile.ZipFile(wheel).namelist()
108
+ assert any(name.endswith("/licenses/LICENSE") for name in names), names
109
+ PY
110
+ - run: python -m venv /tmp/schemarouter-wheel
111
+ - run: /tmp/schemarouter-wheel/bin/python -m pip install --upgrade pip
112
+ - run: /tmp/schemarouter-wheel/bin/pip install dist/*.whl
113
+ - run: /tmp/schemarouter-wheel/bin/python -c "from importlib.metadata import version; import schemarouter; assert schemarouter.__version__ == version('schemarouter')"
114
+ - run: /tmp/schemarouter-wheel/bin/python examples/quickstart.py
115
+ - run: /tmp/schemarouter-wheel/bin/python scripts/benchmark_decision_routing.py
116
+ - run: python -m venv /tmp/schemarouter-sdist
117
+ - run: /tmp/schemarouter-sdist/bin/python -m pip install --upgrade pip
118
+ - run: /tmp/schemarouter-sdist/bin/pip install dist/*.tar.gz
119
+ - run: /tmp/schemarouter-sdist/bin/python -c "from importlib.metadata import version; import schemarouter; assert schemarouter.__version__ == version('schemarouter')"
120
+ - run: /tmp/schemarouter-sdist/bin/python examples/quickstart.py
121
+
122
+ langchain-integration:
123
+ runs-on: ubuntu-latest
124
+ steps:
125
+ - uses: actions/checkout@v7
126
+ - uses: actions/setup-python@v7
127
+ with:
128
+ python-version: "3.12"
129
+ - run: python -m pip install --upgrade pip
130
+ - run: pip install -e ".[dev,langchain]"
131
+ - run: pytest -q tests/test_langchain_integration.py
132
+ - run: python examples/langchain_quickstart.py
133
+
134
+ llamaindex-integration:
135
+ runs-on: ubuntu-latest
136
+ steps:
137
+ - uses: actions/checkout@v7
138
+ - uses: actions/setup-python@v7
139
+ with:
140
+ python-version: "3.12"
141
+ - run: python -m pip install --upgrade pip
142
+ - run: pip install -e ".[dev,llamaindex]"
143
+ - run: pytest -q tests/test_llamaindex_integration.py
144
+ - run: python examples/llamaindex_quickstart.py
145
+
146
+ jev-integration:
147
+ runs-on: ubuntu-latest
148
+ steps:
149
+ - uses: actions/checkout@v7
150
+ - uses: actions/setup-python@v7
151
+ with:
152
+ python-version: "3.12"
153
+ - run: python -m pip install --upgrade pip
154
+ - run: pip install -e ".[dev,jev]"
155
+ - run: pytest -q tests/test_jev_integration.py
156
+
157
+ otel-integration:
158
+ runs-on: ubuntu-latest
159
+ steps:
160
+ - uses: actions/checkout@v7
161
+ - uses: actions/setup-python@v7
162
+ with:
163
+ python-version: "3.12"
164
+ - run: python -m pip install --upgrade pip
165
+ - run: pip install -e ".[dev,otel]"
166
+ - run: pytest -q tests/test_opentelemetry_integration.py
167
+
168
+ mcp-integration:
169
+ runs-on: ubuntu-latest
170
+ steps:
171
+ - uses: actions/checkout@v7
172
+ - uses: actions/setup-python@v7
173
+ with:
174
+ python-version: "3.12"
175
+ - run: python -m pip install --upgrade pip
176
+ - run: pip install -e ".[dev,mcp]"
177
+ - run: pytest -q tests/test_mcp_integration.py
178
+
179
+ docs:
180
+ runs-on: ubuntu-latest
181
+ steps:
182
+ - uses: actions/checkout@v7
183
+ - uses: actions/setup-python@v7
184
+ with:
185
+ python-version: "3.12"
186
+ - run: python -m pip install --upgrade pip
187
+ - run: pip install -e ".[docs]"
188
+ - run: mkdocs build --strict
189
+ env:
190
+ NO_MKDOCS_2_WARNING: "1"
@@ -0,0 +1,30 @@
1
+ name: Python Preview
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ workflow_dispatch:
8
+
9
+ permissions:
10
+ contents: read
11
+
12
+ concurrency:
13
+ group: python-preview-${{ github.event.pull_request.number || github.ref }}
14
+ cancel-in-progress: true
15
+
16
+ jobs:
17
+ python-preview:
18
+ runs-on: ubuntu-latest
19
+ timeout-minutes: 20
20
+ steps:
21
+ - uses: actions/checkout@v7
22
+ - uses: actions/setup-python@v7
23
+ with:
24
+ python-version: "3.15.0-rc.2"
25
+ allow-prereleases: true
26
+ - run: python -m pip install --upgrade pip
27
+ - run: pip install -e ".[dev]"
28
+ - run: ruff check .
29
+ - run: pytest -q -m "not mcp_integration"
30
+ - run: python examples/quickstart.py
@@ -0,0 +1,353 @@
1
+ name: Release
2
+
3
+ on:
4
+ workflow_run:
5
+ workflows: ["CI"]
6
+ types: [completed]
7
+ push:
8
+ tags:
9
+ - "v*"
10
+
11
+ permissions:
12
+ contents: read
13
+
14
+ concurrency:
15
+ group: release-${{ github.repository }}
16
+ cancel-in-progress: false
17
+
18
+ jobs:
19
+ prepare:
20
+ if: >-
21
+ (github.event_name == 'workflow_run' &&
22
+ github.event.workflow_run.conclusion == 'success' &&
23
+ github.event.workflow_run.head_branch == 'main') ||
24
+ (github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v'))
25
+ runs-on: ubuntu-latest
26
+ permissions:
27
+ contents: write
28
+ outputs:
29
+ should_release: ${{ steps.state.outputs.should_release }}
30
+ version: ${{ steps.metadata.outputs.version }}
31
+ tag: ${{ steps.metadata.outputs.tag }}
32
+ release_sha: ${{ steps.metadata.outputs.release_sha }}
33
+ release_notes: ${{ steps.metadata.outputs.release_notes }}
34
+ prerelease: ${{ steps.metadata.outputs.prerelease }}
35
+ steps:
36
+ - uses: actions/checkout@v7
37
+ with:
38
+ ref: ${{ github.event_name == 'workflow_run' && github.event.workflow_run.head_sha || github.ref_name }}
39
+ fetch-depth: 0
40
+
41
+ - name: Verify successful CI still refers to current main
42
+ if: github.event_name == 'workflow_run'
43
+ env:
44
+ TESTED_SHA: ${{ github.event.workflow_run.head_sha }}
45
+ run: |
46
+ git fetch origin main --tags
47
+ current_main="$(git rev-parse origin/main)"
48
+ if [[ "$current_main" != "$TESTED_SHA" ]]; then
49
+ echo "CI passed for stale main SHA $TESTED_SHA; current main is $current_main."
50
+ echo "SKIP_RELEASE=true" >> "$GITHUB_ENV"
51
+ fi
52
+
53
+ - name: Resolve release metadata and target SHA
54
+ id: metadata
55
+ env:
56
+ EVENT_NAME: ${{ github.event_name }}
57
+ TESTED_SHA: ${{ github.event.workflow_run.head_sha }}
58
+ PUSHED_TAG: ${{ github.ref_name }}
59
+ run: |
60
+ python - <<'PY'
61
+ import os
62
+ import re
63
+ import subprocess
64
+ import tomllib
65
+ from pathlib import Path
66
+
67
+ output_path = Path(os.environ["GITHUB_OUTPUT"])
68
+
69
+ def emit(**values: object) -> None:
70
+ with output_path.open("a", encoding="utf-8") as output:
71
+ for key, value in values.items():
72
+ print(f"{key}={value}", file=output)
73
+
74
+ if os.environ.get("SKIP_RELEASE") == "true":
75
+ emit(releasable="false")
76
+ raise SystemExit(0)
77
+
78
+ def read_version_at(ref: str | None = None) -> str:
79
+ if ref is None:
80
+ content = Path("pyproject.toml").read_bytes()
81
+ else:
82
+ content = subprocess.check_output(
83
+ ["git", "show", f"{ref}:pyproject.toml"]
84
+ )
85
+ return tomllib.loads(content.decode("utf-8"))["project"]["version"]
86
+
87
+ version = read_version_at()
88
+ if ".dev" in version:
89
+ emit(releasable="false")
90
+ raise SystemExit(0)
91
+
92
+ if not re.fullmatch(r"\d+\.\d+\.\d+(?:(?:a|b|rc)\d+)?", version):
93
+ raise SystemExit(
94
+ f"unsupported automatic release version format: {version!r}"
95
+ )
96
+
97
+ release_notes = Path("docs") / "releases" / f"{version}.md"
98
+ if not release_notes.is_file():
99
+ raise SystemExit(f"missing release notes: {release_notes}")
100
+
101
+ changelog = Path("CHANGELOG.md").read_text(encoding="utf-8")
102
+ if f"## {version} -" not in changelog:
103
+ raise SystemExit(
104
+ f"CHANGELOG.md is missing a dated {version} release heading"
105
+ )
106
+
107
+ tag = f"v{version}"
108
+ event_name = os.environ["EVENT_NAME"]
109
+
110
+ if event_name == "push":
111
+ pushed_tag = os.environ["PUSHED_TAG"]
112
+ if pushed_tag != tag:
113
+ raise SystemExit(
114
+ f"tag {pushed_tag!r} does not match package version {tag!r}"
115
+ )
116
+ subprocess.check_call(["git", "fetch", "origin", "tag", tag, "--force"])
117
+ release_sha = subprocess.check_output(
118
+ ["git", "rev-list", "-n", "1", tag],
119
+ text=True,
120
+ ).strip()
121
+ else:
122
+ tested_sha = os.environ["TESTED_SHA"]
123
+ remote = subprocess.run(
124
+ [
125
+ "git",
126
+ "ls-remote",
127
+ "--exit-code",
128
+ "--tags",
129
+ "origin",
130
+ f"refs/tags/{tag}",
131
+ ],
132
+ stdout=subprocess.DEVNULL,
133
+ stderr=subprocess.DEVNULL,
134
+ check=False,
135
+ )
136
+ if remote.returncode == 0:
137
+ subprocess.check_call(
138
+ ["git", "fetch", "origin", "tag", tag, "--force"]
139
+ )
140
+ release_sha = subprocess.check_output(
141
+ ["git", "rev-list", "-n", "1", tag],
142
+ text=True,
143
+ ).strip()
144
+ if release_sha != tested_sha:
145
+ ancestor = subprocess.run(
146
+ [
147
+ "git",
148
+ "merge-base",
149
+ "--is-ancestor",
150
+ release_sha,
151
+ tested_sha,
152
+ ],
153
+ check=False,
154
+ )
155
+ if ancestor.returncode != 0:
156
+ raise SystemExit(
157
+ f"existing {tag} points to {release_sha}, which is not "
158
+ f"an ancestor of green main {tested_sha}"
159
+ )
160
+ tagged_version = read_version_at(release_sha)
161
+ if tagged_version != version:
162
+ raise SystemExit(
163
+ f"existing {tag} contains package version "
164
+ f"{tagged_version!r}, expected {version!r}"
165
+ )
166
+ else:
167
+ release_sha = tested_sha
168
+
169
+ prerelease = bool(re.search(r"(?:a|b|rc)\d+(?:$|[.+-])", version))
170
+ emit(
171
+ releasable="true",
172
+ version=version,
173
+ tag=tag,
174
+ release_sha=release_sha,
175
+ release_notes=release_notes.as_posix(),
176
+ prerelease="true" if prerelease else "false",
177
+ )
178
+ PY
179
+
180
+ - name: Create annotated tag when missing
181
+ if: >-
182
+ github.event_name == 'workflow_run' &&
183
+ steps.metadata.outputs.releasable == 'true'
184
+ env:
185
+ RELEASE_SHA: ${{ steps.metadata.outputs.release_sha }}
186
+ RELEASE_TAG: ${{ steps.metadata.outputs.tag }}
187
+ RELEASE_VERSION: ${{ steps.metadata.outputs.version }}
188
+ run: |
189
+ if git ls-remote --exit-code --tags origin "refs/tags/$RELEASE_TAG" >/dev/null 2>&1; then
190
+ echo "$RELEASE_TAG already exists."
191
+ exit 0
192
+ fi
193
+
194
+ git config user.name "github-actions[bot]"
195
+ git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
196
+ git tag -a "$RELEASE_TAG" "$RELEASE_SHA" -m "SchemaRouter $RELEASE_VERSION"
197
+ git push origin "refs/tags/$RELEASE_TAG"
198
+
199
+ - name: Check whether GitHub release already exists
200
+ id: state
201
+ if: steps.metadata.outputs.releasable == 'true'
202
+ env:
203
+ GH_TOKEN: ${{ github.token }}
204
+ RELEASE_TAG: ${{ steps.metadata.outputs.tag }}
205
+ run: |
206
+ if gh release view "$RELEASE_TAG" >/dev/null 2>&1; then
207
+ echo "should_release=false" >> "$GITHUB_OUTPUT"
208
+ echo "$RELEASE_TAG is already published on GitHub."
209
+ else
210
+ echo "should_release=true" >> "$GITHUB_OUTPUT"
211
+ fi
212
+
213
+ build:
214
+ needs: prepare
215
+ if: needs.prepare.outputs.should_release == 'true'
216
+ runs-on: ubuntu-latest
217
+ outputs:
218
+ version: ${{ needs.prepare.outputs.version }}
219
+ release_notes: ${{ needs.prepare.outputs.release_notes }}
220
+ prerelease: ${{ needs.prepare.outputs.prerelease }}
221
+ tag: ${{ needs.prepare.outputs.tag }}
222
+ release_sha: ${{ needs.prepare.outputs.release_sha }}
223
+ steps:
224
+ - uses: actions/checkout@v7
225
+ with:
226
+ ref: ${{ needs.prepare.outputs.release_sha }}
227
+ - uses: actions/setup-python@v7
228
+ with:
229
+ python-version: "3.12"
230
+ - name: Verify release checkout metadata
231
+ env:
232
+ RELEASE_VERSION: ${{ needs.prepare.outputs.version }}
233
+ run: |
234
+ python - <<'PY'
235
+ import os
236
+ import tomllib
237
+ from pathlib import Path
238
+
239
+ version = tomllib.loads(Path("pyproject.toml").read_text())["project"]["version"]
240
+ expected = os.environ["RELEASE_VERSION"]
241
+ if version != expected:
242
+ raise SystemExit(
243
+ f"release checkout version {version!r} does not match {expected!r}"
244
+ )
245
+ PY
246
+ - run: python -m pip install --upgrade pip
247
+ - run: pip install build twine
248
+ - run: python -m build
249
+ - run: twine check dist/*
250
+ - name: Verify release artifacts
251
+ env:
252
+ RELEASE_VERSION: ${{ needs.prepare.outputs.version }}
253
+ run: |
254
+ python - <<'PY'
255
+ import glob
256
+ import os
257
+ import subprocess
258
+ import sys
259
+ import tempfile
260
+ import venv
261
+ import zipfile
262
+ from pathlib import Path
263
+
264
+ wheel = glob.glob("dist/*.whl")[0]
265
+ sdist = glob.glob("dist/*.tar.gz")[0]
266
+
267
+ wheel_names = zipfile.ZipFile(wheel).namelist()
268
+ assert any(name.endswith("/licenses/LICENSE") for name in wheel_names), wheel_names
269
+
270
+ import tarfile
271
+
272
+ with tarfile.open(sdist, "r:gz") as archive:
273
+ sdist_names = archive.getnames()
274
+ assert any(name.endswith("/LICENSE") for name in sdist_names), sdist_names
275
+
276
+ for artifact in (wheel, sdist):
277
+ with tempfile.TemporaryDirectory() as root:
278
+ env = Path(root) / "venv"
279
+ venv.EnvBuilder(with_pip=True).create(env)
280
+ python = env / (
281
+ "Scripts/python.exe" if sys.platform == "win32" else "bin/python"
282
+ )
283
+ subprocess.check_call([str(python), "-m", "pip", "install", artifact])
284
+ subprocess.check_call(
285
+ [
286
+ str(python),
287
+ "-c",
288
+ (
289
+ "from importlib.metadata import version; "
290
+ "import schemarouter; "
291
+ f"assert schemarouter.__version__ == "
292
+ f"{os.environ['RELEASE_VERSION']!r}; "
293
+ f"assert version('schemarouter') == "
294
+ f"{os.environ['RELEASE_VERSION']!r}"
295
+ ),
296
+ ]
297
+ )
298
+ subprocess.check_call([str(python), "examples/quickstart.py"])
299
+ PY
300
+ - uses: actions/upload-artifact@v7
301
+ with:
302
+ name: python-dist
303
+ path: dist/
304
+ if-no-files-found: error
305
+ retention-days: 7
306
+
307
+ github-release:
308
+ needs: [prepare, build]
309
+ if: needs.prepare.outputs.should_release == 'true'
310
+ permissions:
311
+ contents: write
312
+ runs-on: ubuntu-latest
313
+ steps:
314
+ - uses: actions/checkout@v7
315
+ with:
316
+ ref: ${{ needs.prepare.outputs.release_sha }}
317
+ - uses: actions/download-artifact@v8
318
+ with:
319
+ name: python-dist
320
+ path: dist
321
+ - name: Create GitHub release
322
+ env:
323
+ GH_TOKEN: ${{ github.token }}
324
+ RELEASE_TAG: ${{ needs.prepare.outputs.tag }}
325
+ RELEASE_VERSION: ${{ needs.prepare.outputs.version }}
326
+ RELEASE_NOTES: ${{ needs.prepare.outputs.release_notes }}
327
+ IS_PRERELEASE: ${{ needs.prepare.outputs.prerelease }}
328
+ run: |
329
+ prerelease_args=()
330
+ if [[ "$IS_PRERELEASE" == "true" ]]; then
331
+ prerelease_args+=(--prerelease)
332
+ fi
333
+
334
+ gh release create "$RELEASE_TAG" dist/* --title "SchemaRouter $RELEASE_VERSION" --notes-file "$RELEASE_NOTES" "${prerelease_args[@]}" --verify-tag
335
+
336
+ pypi:
337
+ needs: [prepare, build]
338
+ if: needs.prepare.outputs.should_release == 'true'
339
+ permissions:
340
+ id-token: write
341
+ runs-on: ubuntu-latest
342
+ environment:
343
+ name: pypi
344
+ url: https://pypi.org/project/schemarouter/
345
+ steps:
346
+ - uses: actions/download-artifact@v8
347
+ with:
348
+ name: python-dist
349
+ path: dist
350
+ - uses: pypa/gh-action-pypi-publish@release/v1
351
+ with:
352
+ packages-dir: dist/
353
+ skip-existing: true
@@ -0,0 +1,119 @@
1
+ # Changelog
2
+
3
+ All notable changes to SchemaRouter are documented here.
4
+
5
+ The project is pre-1.0 and follows the compatibility rules in
6
+ [`docs/versioning.md`](docs/versioning.md).
7
+
8
+ ## Unreleased
9
+
10
+ ## 0.3.0 - 2026-09-22
11
+
12
+ ### Changed
13
+
14
+ - promoted the 0.3 line to the first non-prerelease release after the 0.3.0a1 validation cycle;
15
+ - standard PyPI installation no longer requires `--pre`;
16
+ - package maturity metadata now identifies the project as beta while retaining the documented
17
+ pre-1.0 compatibility policy;
18
+ - GitHub Pages deployment and protected-main required-check enforcement are now active repository
19
+ controls.
20
+
21
+ ### Compatibility
22
+
23
+ - no intentional public API changes were introduced relative to 0.3.0a1;
24
+ - the 0.x line remains pre-1.0, so later minor releases may still include deliberate, documented
25
+ compatibility changes.
26
+
27
+ ## 0.3.0a1 - 2026-09-22
28
+
29
+ ### Added
30
+
31
+ - provider-neutral bounded `DecisionBackend` contracts with deterministic fallback;
32
+ - optional `JevDecisionBackend` backed by TypeSafe System One through `schemarouter[jev]`;
33
+ - optional LlamaIndex `FunctionTool` integration;
34
+ - runnable LangChain and LlamaIndex integration examples enforced in CI;
35
+ - provider-neutral decision-routing benchmark harness covering accuracy, abstention, latency,
36
+ token usage, and optional cost estimation;
37
+ - explicit ecosystem compatibility matrix and integration maintenance policy;
38
+ - Korean project overview in `README.ko.md`;
39
+ - Python 3.13 and 3.14 core CI coverage;
40
+ - a non-blocking Python 3.15 release-candidate preview;
41
+ - a Windows + Python 3.14 smoke surface;
42
+ - a Pyright static-type gate for the typed package surface;
43
+ - full-suite branch-coverage reporting with an 82% blocking floor;
44
+ - declared lower-bound runtime dependency testing;
45
+ - clean wheel and sdist installation smoke tests;
46
+ - a checked-in 144-case multilingual/adversarial decision-routing corpus with JSON/CSV output,
47
+ category metrics, invalid-plan rate, abstention/fallback tracking, and p50/p95 latency;
48
+ - machine-readable OpenAPI compatibility reports for partial/unsupported constructs;
49
+ - authenticated MCP trusted-header and custom client-factory transport boundaries;
50
+ - trusted sync/async per-call approval callbacks;
51
+ - per-run execution budgets for logical calls, attempts, remote attempts, elapsed time, per-tool
52
+ quotas, and application-defined cost units;
53
+ - optional privacy-preserving OpenTelemetry run/tool span export;
54
+ - explicit allowlisted third-party adapter plugins through `schemarouter.adapters` entry points.
55
+
56
+ ### Changed
57
+
58
+ - release preparation now uses explicit PEP 440 release versions while normal post-release
59
+ development returns to a `.dev0` version;
60
+ - release automation now derives version, release title, release-note path, prerelease state, and
61
+ artifact verification from `pyproject.toml` instead of hard-coded release literals;
62
+ - release publication now consumes a successful current-`main` CI result, verifies or creates the
63
+ annotated version tag, and clean-installs both wheel and sdist artifacts before publication;
64
+ - superseded pull-request CI runs are cancelled automatically to avoid stale validation consuming
65
+ runner capacity;
66
+ - releasable `main` commits are automatically published by the top-level Release workflow only
67
+ after green CI, current-head verification, release-note/changelog validation, and duplicate-tag
68
+ checks; existing unpublished tags may be resumed only from a compatible ancestor SHA.
69
+
70
+ ### Security
71
+
72
+ - OpenAPI runtime responses are now streamed through a bounded reader with a 16 MiB default limit,
73
+ enforcing the bound both from declared `Content-Length` and actual bytes received;
74
+ - Jev option IDs are validated before confidence-based abstention, so unknown IDs always fail
75
+ closed;
76
+ - Jev credentials remain SDK client configuration and are never placed in model state;
77
+ - `DecisionOption.metadata` is not forwarded to Jev;
78
+ - provider failures can deterministically fall back without weakening execution policy;
79
+ - MCP credentials are rejected in URLs and remain outside tool/planner metadata;
80
+ - MCP protocol-controlled headers cannot be overridden through trusted runtime headers;
81
+ - adapter entry points are metadata-only during discovery and are never auto-imported;
82
+ - OpenTelemetry export omits payload values, RunConfig metadata, tags, and exception messages;
83
+ - approval callback failures and execution budget exhaustion fail closed.
84
+
85
+ ## 0.2.0a1 - 2026-09-20
86
+
87
+ ### Added
88
+
89
+ - pluggable structured-source `AdapterRegistry` with explicit and priority-based auto discovery;
90
+ - OPTIMADE v1 discovery through base and entry-type info endpoints;
91
+ - OPTIMADE field-aware execution that maps planned fields to `response_fields`;
92
+ - call-aware invoker support for protocol adapters that need the full `ToolCall`;
93
+ - typed tool, endpoint, parameter, response-field, plan, and result contracts;
94
+ - namespaced versioned registry;
95
+ - schema-aware planning with recall-preserving field projection;
96
+ - provider-neutral model-assisted query analysis;
97
+ - OpenAPI 3.x URL ingestion and guarded HTTP execution;
98
+ - MCP tool discovery and execution;
99
+ - evidence-grounded proposals for human-readable API documentation;
100
+ - runtime JSON Schema validation for arguments and raw outputs;
101
+ - schema and invoker-binding drift detection;
102
+ - fail-closed execution policy for mutations, destructive calls, and unclassified remote tools;
103
+ - Runnable-style `invoke`, `batch`, `stream`, and typed event APIs;
104
+ - run configuration, concurrency control, and safe retry policy;
105
+ - typed Python callable registration with `@schema_tool`;
106
+ - optional LangChain `StructuredTool` integration;
107
+ - framework maturity, architecture, release, and versioning documentation;
108
+ - MIT licensing and package metadata;
109
+ - MkDocs Material documentation site with guides, recipes, and generated API reference;
110
+ - SchemaRouter brace-and-routing-hub brand system with light/dark marks, lockups, favicon, and
111
+ social preview source artwork.
112
+
113
+ ### Security
114
+
115
+ - separated schema-fetch credentials from runtime API credentials;
116
+ - restricted schema redirects and runtime API origins;
117
+ - blocked model/tool control of sensitive runtime headers;
118
+ - redacted event payloads by default;
119
+ - prevented automatic retries for non-read-only calls.