gatewaysdk 0.3.2__py3-none-any.whl

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 (376) hide show
  1. gatewaysdk/__init__.py +540 -0
  2. gatewaysdk/adapter/__init__.py +15 -0
  3. gatewaysdk/adapter/base.py +94 -0
  4. gatewaysdk/adapter/messages.py +270 -0
  5. gatewaysdk/adapter/triplet.py +1026 -0
  6. gatewaysdk/algorithm/__init__.py +39 -0
  7. gatewaysdk/algorithm/apo/__init__.py +5 -0
  8. gatewaysdk/algorithm/apo/apo.py +898 -0
  9. gatewaysdk/algorithm/apo/prompts/apply_edit_variant01.poml +22 -0
  10. gatewaysdk/algorithm/apo/prompts/apply_edit_variant02.poml +18 -0
  11. gatewaysdk/algorithm/apo/prompts/text_gradient_variant01.poml +18 -0
  12. gatewaysdk/algorithm/apo/prompts/text_gradient_variant02.poml +16 -0
  13. gatewaysdk/algorithm/apo/prompts/text_gradient_variant03.poml +107 -0
  14. gatewaysdk/algorithm/base.py +262 -0
  15. gatewaysdk/algorithm/decorator.py +264 -0
  16. gatewaysdk/algorithm/evals/__init__.py +7 -0
  17. gatewaysdk/algorithm/evals/evals.py +217 -0
  18. gatewaysdk/algorithm/fast.py +250 -0
  19. gatewaysdk/algorithm/gepa/__init__.py +61 -0
  20. gatewaysdk/algorithm/gepa/adapter.py +495 -0
  21. gatewaysdk/algorithm/gepa/gepa.py +570 -0
  22. gatewaysdk/algorithm/gepa/lib/__init__.py +18 -0
  23. gatewaysdk/algorithm/gepa/lib/adapters/README.md +12 -0
  24. gatewaysdk/algorithm/gepa/lib/adapters/__init__.py +0 -0
  25. gatewaysdk/algorithm/gepa/lib/adapters/anymaths_adapter/README.md +341 -0
  26. gatewaysdk/algorithm/gepa/lib/adapters/anymaths_adapter/__init__.py +1 -0
  27. gatewaysdk/algorithm/gepa/lib/adapters/anymaths_adapter/anymaths_adapter.py +174 -0
  28. gatewaysdk/algorithm/gepa/lib/adapters/anymaths_adapter/requirements.txt +1 -0
  29. gatewaysdk/algorithm/gepa/lib/adapters/default_adapter/README.md +0 -0
  30. gatewaysdk/algorithm/gepa/lib/adapters/default_adapter/__init__.py +0 -0
  31. gatewaysdk/algorithm/gepa/lib/adapters/default_adapter/default_adapter.py +209 -0
  32. gatewaysdk/algorithm/gepa/lib/adapters/dspy_adapter/README.md +7 -0
  33. gatewaysdk/algorithm/gepa/lib/adapters/dspy_adapter/__init__.py +0 -0
  34. gatewaysdk/algorithm/gepa/lib/adapters/dspy_adapter/dspy_adapter.py +307 -0
  35. gatewaysdk/algorithm/gepa/lib/adapters/dspy_full_program_adapter/README.md +99 -0
  36. gatewaysdk/algorithm/gepa/lib/adapters/dspy_full_program_adapter/dspy_program_proposal_signature.py +137 -0
  37. gatewaysdk/algorithm/gepa/lib/adapters/dspy_full_program_adapter/full_program_adapter.py +268 -0
  38. gatewaysdk/algorithm/gepa/lib/adapters/generic_rag_adapter/GEPA_RAG.md +621 -0
  39. gatewaysdk/algorithm/gepa/lib/adapters/generic_rag_adapter/__init__.py +56 -0
  40. gatewaysdk/algorithm/gepa/lib/adapters/generic_rag_adapter/evaluation_metrics.py +226 -0
  41. gatewaysdk/algorithm/gepa/lib/adapters/generic_rag_adapter/generic_rag_adapter.py +496 -0
  42. gatewaysdk/algorithm/gepa/lib/adapters/generic_rag_adapter/rag_pipeline.py +238 -0
  43. gatewaysdk/algorithm/gepa/lib/adapters/generic_rag_adapter/vector_store_interface.py +212 -0
  44. gatewaysdk/algorithm/gepa/lib/adapters/generic_rag_adapter/vector_stores/__init__.py +2 -0
  45. gatewaysdk/algorithm/gepa/lib/adapters/generic_rag_adapter/vector_stores/chroma_store.py +196 -0
  46. gatewaysdk/algorithm/gepa/lib/adapters/generic_rag_adapter/vector_stores/lancedb_store.py +422 -0
  47. gatewaysdk/algorithm/gepa/lib/adapters/generic_rag_adapter/vector_stores/milvus_store.py +409 -0
  48. gatewaysdk/algorithm/gepa/lib/adapters/generic_rag_adapter/vector_stores/qdrant_store.py +368 -0
  49. gatewaysdk/algorithm/gepa/lib/adapters/generic_rag_adapter/vector_stores/weaviate_store.py +418 -0
  50. gatewaysdk/algorithm/gepa/lib/adapters/mcp_adapter/README.md +552 -0
  51. gatewaysdk/algorithm/gepa/lib/adapters/mcp_adapter/__init__.py +37 -0
  52. gatewaysdk/algorithm/gepa/lib/adapters/mcp_adapter/mcp_adapter.py +699 -0
  53. gatewaysdk/algorithm/gepa/lib/adapters/mcp_adapter/mcp_client.py +364 -0
  54. gatewaysdk/algorithm/gepa/lib/adapters/terminal_bench_adapter/README.md +9 -0
  55. gatewaysdk/algorithm/gepa/lib/adapters/terminal_bench_adapter/__init__.py +0 -0
  56. gatewaysdk/algorithm/gepa/lib/adapters/terminal_bench_adapter/terminal_bench_adapter.py +217 -0
  57. gatewaysdk/algorithm/gepa/lib/api.py +382 -0
  58. gatewaysdk/algorithm/gepa/lib/core/__init__.py +0 -0
  59. gatewaysdk/algorithm/gepa/lib/core/adapter.py +180 -0
  60. gatewaysdk/algorithm/gepa/lib/core/data_loader.py +74 -0
  61. gatewaysdk/algorithm/gepa/lib/core/engine.py +379 -0
  62. gatewaysdk/algorithm/gepa/lib/core/result.py +233 -0
  63. gatewaysdk/algorithm/gepa/lib/core/state.py +636 -0
  64. gatewaysdk/algorithm/gepa/lib/examples/__init__.py +0 -0
  65. gatewaysdk/algorithm/gepa/lib/examples/aime.py +24 -0
  66. gatewaysdk/algorithm/gepa/lib/examples/anymaths-bench/eval_default.py +111 -0
  67. gatewaysdk/algorithm/gepa/lib/examples/anymaths-bench/prompt-templates/instruction_prompt.txt +9 -0
  68. gatewaysdk/algorithm/gepa/lib/examples/anymaths-bench/prompt-templates/optimal_prompt.txt +24 -0
  69. gatewaysdk/algorithm/gepa/lib/examples/anymaths-bench/train_anymaths.py +177 -0
  70. gatewaysdk/algorithm/gepa/lib/examples/dspy_full_program_evolution/arc_agi.ipynb +25705 -0
  71. gatewaysdk/algorithm/gepa/lib/examples/dspy_full_program_evolution/example.ipynb +348 -0
  72. gatewaysdk/algorithm/gepa/lib/examples/mcp_adapter/__init__.py +4 -0
  73. gatewaysdk/algorithm/gepa/lib/examples/mcp_adapter/mcp_optimization_example.py +456 -0
  74. gatewaysdk/algorithm/gepa/lib/examples/rag_adapter/RAG_GUIDE.md +613 -0
  75. gatewaysdk/algorithm/gepa/lib/examples/rag_adapter/__init__.py +9 -0
  76. gatewaysdk/algorithm/gepa/lib/examples/rag_adapter/rag_optimization.py +820 -0
  77. gatewaysdk/algorithm/gepa/lib/examples/rag_adapter/requirements-rag.txt +29 -0
  78. gatewaysdk/algorithm/gepa/lib/examples/terminal-bench/prompt-templates/instruction_prompt.txt +16 -0
  79. gatewaysdk/algorithm/gepa/lib/examples/terminal-bench/prompt-templates/terminus.txt +9 -0
  80. gatewaysdk/algorithm/gepa/lib/examples/terminal-bench/train_terminus.py +161 -0
  81. gatewaysdk/algorithm/gepa/lib/gepa_utils.py +117 -0
  82. gatewaysdk/algorithm/gepa/lib/logging/__init__.py +0 -0
  83. gatewaysdk/algorithm/gepa/lib/logging/experiment_tracker.py +187 -0
  84. gatewaysdk/algorithm/gepa/lib/logging/logger.py +75 -0
  85. gatewaysdk/algorithm/gepa/lib/logging/utils.py +103 -0
  86. gatewaysdk/algorithm/gepa/lib/proposer/__init__.py +0 -0
  87. gatewaysdk/algorithm/gepa/lib/proposer/base.py +31 -0
  88. gatewaysdk/algorithm/gepa/lib/proposer/merge.py +357 -0
  89. gatewaysdk/algorithm/gepa/lib/proposer/reflective_mutation/__init__.py +0 -0
  90. gatewaysdk/algorithm/gepa/lib/proposer/reflective_mutation/base.py +49 -0
  91. gatewaysdk/algorithm/gepa/lib/proposer/reflective_mutation/reflective_mutation.py +176 -0
  92. gatewaysdk/algorithm/gepa/lib/py.typed +0 -0
  93. gatewaysdk/algorithm/gepa/lib/strategies/__init__.py +0 -0
  94. gatewaysdk/algorithm/gepa/lib/strategies/batch_sampler.py +77 -0
  95. gatewaysdk/algorithm/gepa/lib/strategies/candidate_selector.py +50 -0
  96. gatewaysdk/algorithm/gepa/lib/strategies/component_selector.py +36 -0
  97. gatewaysdk/algorithm/gepa/lib/strategies/eval_policy.py +64 -0
  98. gatewaysdk/algorithm/gepa/lib/strategies/instruction_proposal.py +126 -0
  99. gatewaysdk/algorithm/gepa/lib/utils/__init__.py +10 -0
  100. gatewaysdk/algorithm/gepa/lib/utils/stop_condition.py +196 -0
  101. gatewaysdk/algorithm/gepa/tracing.py +105 -0
  102. gatewaysdk/algorithm/utils.py +177 -0
  103. gatewaysdk/algorithm/verl/__init__.py +5 -0
  104. gatewaysdk/algorithm/verl/interface.py +202 -0
  105. gatewaysdk/automations.py +111 -0
  106. gatewaysdk/benchmark_hub/README.md +88 -0
  107. gatewaysdk/benchmark_hub/__init__.py +119 -0
  108. gatewaysdk/benchmark_hub/_archive.py +189 -0
  109. gatewaysdk/benchmark_hub/_tar.py +6 -0
  110. gatewaysdk/benchmark_hub/client.py +992 -0
  111. gatewaysdk/benchmark_hub/dispatch_shard.py +34 -0
  112. gatewaysdk/benchmark_hub/eval_config.py +20 -0
  113. gatewaysdk/benchmark_hub/evals.py +351 -0
  114. gatewaysdk/benchmark_hub/harbor_adapter.py +673 -0
  115. gatewaysdk/benchmark_hub/path_utils.py +81 -0
  116. gatewaysdk/benchmark_hub/save_utils.py +101 -0
  117. gatewaysdk/benchmark_hub/verifiers_adapter.py +327 -0
  118. gatewaysdk/benchmark_hub/versioning.py +72 -0
  119. gatewaysdk/build.py +515 -0
  120. gatewaysdk/cli/__init__.py +58 -0
  121. gatewaysdk/cli/agent_runner.py +132 -0
  122. gatewaysdk/cli/http_client.py +115 -0
  123. gatewaysdk/cli/platform.py +4086 -0
  124. gatewaysdk/cli/prometheus.py +115 -0
  125. gatewaysdk/cli/release_gate.py +215 -0
  126. gatewaysdk/cli/store.py +131 -0
  127. gatewaysdk/cli/vllm.py +29 -0
  128. gatewaysdk/client.py +406 -0
  129. gatewaysdk/config.py +348 -0
  130. gatewaysdk/connectors/__init__.py +25 -0
  131. gatewaysdk/connectors/client.py +203 -0
  132. gatewaysdk/connectors/skill.py +25 -0
  133. gatewaysdk/connectors/template.py +106 -0
  134. gatewaysdk/context.py +606 -0
  135. gatewaysdk/emitter/__init__.py +43 -0
  136. gatewaysdk/emitter/annotation.py +370 -0
  137. gatewaysdk/emitter/exception.py +54 -0
  138. gatewaysdk/emitter/message.py +61 -0
  139. gatewaysdk/emitter/object.py +117 -0
  140. gatewaysdk/emitter/reward.py +320 -0
  141. gatewaysdk/env_var.py +156 -0
  142. gatewaysdk/environment/__init__.py +108 -0
  143. gatewaysdk/environment/_bundle.py +281 -0
  144. gatewaysdk/environment/_harbor.py +276 -0
  145. gatewaysdk/environment/_materialize.py +97 -0
  146. gatewaysdk/environment/_tar.py +33 -0
  147. gatewaysdk/environment/_world.py +346 -0
  148. gatewaysdk/environment/core.py +527 -0
  149. gatewaysdk/environment/errors.py +58 -0
  150. gatewaysdk/environment/runtime.py +150 -0
  151. gatewaysdk/environment/schema/__init__.py +35 -0
  152. gatewaysdk/environment/schema/__main__.py +225 -0
  153. gatewaysdk/environment/schema/_slack_fidelity.py +116 -0
  154. gatewaysdk/environment/schema/_slack_scenario.py +183 -0
  155. gatewaysdk/environment/schema/api.py +1628 -0
  156. gatewaysdk/environment/schema/batch.py +755 -0
  157. gatewaysdk/environment/schema/compiler.py +615 -0
  158. gatewaysdk/environment/schema/conform.py +428 -0
  159. gatewaysdk/environment/schema/connector.py +777 -0
  160. gatewaysdk/environment/schema/connectors/apple-business-manager/handlers.py +17 -0
  161. gatewaysdk/environment/schema/connectors/apple-business-manager/parity.json +28 -0
  162. gatewaysdk/environment/schema/connectors/apple-business-manager/provenance.json +233 -0
  163. gatewaysdk/environment/schema/connectors/apple-business-manager/scope.toml +112 -0
  164. gatewaysdk/environment/schema/connectors/apple-business-manager/world.json +1380 -0
  165. gatewaysdk/environment/schema/connectors/base.json +28 -0
  166. gatewaysdk/environment/schema/connectors/custom/handlers.py +18 -0
  167. gatewaysdk/environment/schema/connectors/custom/world.json +123 -0
  168. gatewaysdk/environment/schema/connectors/github/handlers.py +81 -0
  169. gatewaysdk/environment/schema/connectors/github/parity.json +37 -0
  170. gatewaysdk/environment/schema/connectors/github/provenance.json +134 -0
  171. gatewaysdk/environment/schema/connectors/github/scope.toml +158 -0
  172. gatewaysdk/environment/schema/connectors/github/world.json +5869 -0
  173. gatewaysdk/environment/schema/connectors/google-calendar/handlers.py +23 -0
  174. gatewaysdk/environment/schema/connectors/google-calendar/world.json +209 -0
  175. gatewaysdk/environment/schema/connectors/google-drive/handlers.py +18 -0
  176. gatewaysdk/environment/schema/connectors/google-drive/world.json +286 -0
  177. gatewaysdk/environment/schema/connectors/jamf/handlers.py +34 -0
  178. gatewaysdk/environment/schema/connectors/jamf/parity.json +49 -0
  179. gatewaysdk/environment/schema/connectors/jamf/provenance.json +181 -0
  180. gatewaysdk/environment/schema/connectors/jamf/scope.toml +128 -0
  181. gatewaysdk/environment/schema/connectors/jamf/world.json +2218 -0
  182. gatewaysdk/environment/schema/connectors/jira/handlers.py +28 -0
  183. gatewaysdk/environment/schema/connectors/jira/world.json +389 -0
  184. gatewaysdk/environment/schema/connectors/kandji/handlers.py +38 -0
  185. gatewaysdk/environment/schema/connectors/kandji/parity.json +29 -0
  186. gatewaysdk/environment/schema/connectors/kandji/provenance.json +152 -0
  187. gatewaysdk/environment/schema/connectors/kandji/scope.toml +120 -0
  188. gatewaysdk/environment/schema/connectors/kandji/world.json +765 -0
  189. gatewaysdk/environment/schema/connectors/linear/handlers.py +20 -0
  190. gatewaysdk/environment/schema/connectors/linear/world.json +363 -0
  191. gatewaysdk/environment/schema/connectors/microsoft-teams/fidelity.json +45 -0
  192. gatewaysdk/environment/schema/connectors/microsoft-teams/handlers.py +1 -0
  193. gatewaysdk/environment/schema/connectors/microsoft-teams/provenance.json +851 -0
  194. gatewaysdk/environment/schema/connectors/microsoft-teams/scope.md +231 -0
  195. gatewaysdk/environment/schema/connectors/microsoft-teams/teams-types.json +1710 -0
  196. gatewaysdk/environment/schema/connectors/microsoft-teams/world.json +858 -0
  197. gatewaysdk/environment/schema/connectors/netsuite/handlers.py +32 -0
  198. gatewaysdk/environment/schema/connectors/netsuite/parity.json +50 -0
  199. gatewaysdk/environment/schema/connectors/netsuite/provenance.json +137 -0
  200. gatewaysdk/environment/schema/connectors/netsuite/scope.toml +78 -0
  201. gatewaysdk/environment/schema/connectors/netsuite/world.json +16243 -0
  202. gatewaysdk/environment/schema/connectors/salesforce/handlers.py +806 -0
  203. gatewaysdk/environment/schema/connectors/salesforce/parity.json +46 -0
  204. gatewaysdk/environment/schema/connectors/salesforce/provenance.json +143 -0
  205. gatewaysdk/environment/schema/connectors/salesforce/scope.toml +174 -0
  206. gatewaysdk/environment/schema/connectors/salesforce/world.json +4695 -0
  207. gatewaysdk/environment/schema/connectors/slack/MODEL.md +236 -0
  208. gatewaysdk/environment/schema/connectors/slack/capabilities.json +1039 -0
  209. gatewaysdk/environment/schema/connectors/slack/handlers.py +762 -0
  210. gatewaysdk/environment/schema/connectors/slack/parity.json +65 -0
  211. gatewaysdk/environment/schema/connectors/slack/provenance.json +294 -0
  212. gatewaysdk/environment/schema/connectors/slack/scope.toml +44 -0
  213. gatewaysdk/environment/schema/connectors/slack/world.json +4993 -0
  214. gatewaysdk/environment/schema/connectors/workday/handlers.py +75 -0
  215. gatewaysdk/environment/schema/connectors/workday/parity.json +36 -0
  216. gatewaysdk/environment/schema/connectors/workday/provenance.json +179 -0
  217. gatewaysdk/environment/schema/connectors/workday/scope.toml +180 -0
  218. gatewaysdk/environment/schema/connectors/workday/world.json +806 -0
  219. gatewaysdk/environment/schema/data.py +593 -0
  220. gatewaysdk/environment/schema/extract.py +263 -0
  221. gatewaysdk/environment/schema/host.py +2437 -0
  222. gatewaysdk/environment/schema/host_surfaces.py +275 -0
  223. gatewaysdk/environment/schema/platform.py +1272 -0
  224. gatewaysdk/environment/schema/scaffold.py +250 -0
  225. gatewaysdk/environment/schema/skill.py +118 -0
  226. gatewaysdk/environment/schema/skills/agents/connector-template-author.md +202 -0
  227. gatewaysdk/environment/schema/skills/connector-schema-authoring/CONNECTORS.md +331 -0
  228. gatewaysdk/environment/schema/skills/connector-schema-authoring/CONVENTIONS.md +100 -0
  229. gatewaysdk/environment/schema/skills/connector-schema-authoring/SKILL.md +313 -0
  230. gatewaysdk/environment/schema/skills/world-data-ingestion/ROWS.md +223 -0
  231. gatewaysdk/environment/schema/skills/world-data-ingestion/SKILL.md +293 -0
  232. gatewaysdk/environment/schema/skills/worlds-getting-started/SKILL.md +390 -0
  233. gatewaysdk/environment/schema/slack.py +757 -0
  234. gatewaysdk/environment/schema/snapshot.py +479 -0
  235. gatewaysdk/environment/schema/store.py +904 -0
  236. gatewaysdk/environment/schema/tasks.py +525 -0
  237. gatewaysdk/environment/schema/tools_world.py +322 -0
  238. gatewaysdk/environment/schema/validation.py +577 -0
  239. gatewaysdk/environment/schema/worker.py +78 -0
  240. gatewaysdk/environment/schema/workers.py +500 -0
  241. gatewaysdk/environment/schema/world_tests.py +336 -0
  242. gatewaysdk/environments/__init__.py +24 -0
  243. gatewaysdk/environments/client.py +486 -0
  244. gatewaysdk/environments/types.py +318 -0
  245. gatewaysdk/execution/__init__.py +15 -0
  246. gatewaysdk/execution/base.py +64 -0
  247. gatewaysdk/execution/client_server.py +443 -0
  248. gatewaysdk/execution/events.py +69 -0
  249. gatewaysdk/execution/inter_process.py +16 -0
  250. gatewaysdk/execution/shared_memory.py +282 -0
  251. gatewaysdk/experiments/__init__.py +90 -0
  252. gatewaysdk/experiments/assignment.py +177 -0
  253. gatewaysdk/experiments/client.py +877 -0
  254. gatewaysdk/experiments/exposure.py +222 -0
  255. gatewaysdk/experiments/types.py +81 -0
  256. gatewaysdk/importers/__init__.py +49 -0
  257. gatewaysdk/importers/_normalize.py +91 -0
  258. gatewaysdk/importers/builder.py +173 -0
  259. gatewaysdk/importers/client.py +95 -0
  260. gatewaysdk/importers/langsmith.py +196 -0
  261. gatewaysdk/importers/recipes.py +249 -0
  262. gatewaysdk/instrumentation/__init__.py +300 -0
  263. gatewaysdk/instrumentation/agentops.py +314 -0
  264. gatewaysdk/instrumentation/agentops_langchain.py +45 -0
  265. gatewaysdk/instrumentation/base.py +119 -0
  266. gatewaysdk/instrumentation/litellm.py +83 -0
  267. gatewaysdk/instrumentation/registry.py +273 -0
  268. gatewaysdk/instrumentation/vllm.py +81 -0
  269. gatewaysdk/instrumentation/weave.py +500 -0
  270. gatewaysdk/integrations/__init__.py +15 -0
  271. gatewaysdk/integrations/gateway/__init__.py +11 -0
  272. gatewaysdk/integrations/gateway/client.py +171 -0
  273. gatewaysdk/integrations/tool_access.py +549 -0
  274. gatewaysdk/litagent/__init__.py +11 -0
  275. gatewaysdk/litagent/decorator.py +536 -0
  276. gatewaysdk/litagent/litagent.py +252 -0
  277. gatewaysdk/llm_proxy.py +1742 -0
  278. gatewaysdk/logging.py +370 -0
  279. gatewaysdk/memory.py +278 -0
  280. gatewaysdk/personas.py +186 -0
  281. gatewaysdk/platform/__init__.py +17 -0
  282. gatewaysdk/platform/builder.py +221 -0
  283. gatewaysdk/platform/compatibility.py +67 -0
  284. gatewaysdk/platform/manifest.py +185 -0
  285. gatewaysdk/platform/orchestrator.py +901 -0
  286. gatewaysdk/platform/registry.py +122 -0
  287. gatewaysdk/platform/worker.py +864 -0
  288. gatewaysdk/replay/__init__.py +1032 -0
  289. gatewaysdk/replay/pytest.py +56 -0
  290. gatewaysdk/reward.py +7 -0
  291. gatewaysdk/run.py +1781 -0
  292. gatewaysdk/runner/__init__.py +11 -0
  293. gatewaysdk/runner/agent.py +878 -0
  294. gatewaysdk/runner/base.py +182 -0
  295. gatewaysdk/runner/legacy.py +309 -0
  296. gatewaysdk/security.py +700 -0
  297. gatewaysdk/semconv.py +170 -0
  298. gatewaysdk/server.py +399 -0
  299. gatewaysdk/sessions.py +282 -0
  300. gatewaysdk/store/__init__.py +45 -0
  301. gatewaysdk/store/base.py +908 -0
  302. gatewaysdk/store/client_server.py +2093 -0
  303. gatewaysdk/store/collection/__init__.py +30 -0
  304. gatewaysdk/store/collection/base.py +587 -0
  305. gatewaysdk/store/collection/memory.py +970 -0
  306. gatewaysdk/store/collection/mongo.py +1412 -0
  307. gatewaysdk/store/collection_based.py +1823 -0
  308. gatewaysdk/store/gateway.py +983 -0
  309. gatewaysdk/store/gateway_listener.py +465 -0
  310. gatewaysdk/store/listener.py +58 -0
  311. gatewaysdk/store/memory.py +396 -0
  312. gatewaysdk/store/mongo.py +165 -0
  313. gatewaysdk/store/redis_stream.py +517 -0
  314. gatewaysdk/store/sqlite.py +3 -0
  315. gatewaysdk/store/threading.py +370 -0
  316. gatewaysdk/store/utils.py +142 -0
  317. gatewaysdk/tracer/__init__.py +14 -0
  318. gatewaysdk/tracer/base.py +286 -0
  319. gatewaysdk/tracer/dummy.py +106 -0
  320. gatewaysdk/tracer/otel.py +559 -0
  321. gatewaysdk/tracing/__init__.py +110 -0
  322. gatewaysdk/tracing/api.py +808 -0
  323. gatewaysdk/tracing/attributes.py +9 -0
  324. gatewaysdk/tracing/context.py +272 -0
  325. gatewaysdk/tracing/exporters/__init__.py +10 -0
  326. gatewaysdk/tracing/exporters/gateway.py +228 -0
  327. gatewaysdk/tracing/identity.py +288 -0
  328. gatewaysdk/tracing/init.py +620 -0
  329. gatewaysdk/tracing/instrumentors/__init__.py +15 -0
  330. gatewaysdk/tracing/instrumentors/claude_agent_sdk.py +766 -0
  331. gatewaysdk/tracing/instrumentors/instrumentation_principles.md +294 -0
  332. gatewaysdk/tracing/instrumentors/registry.py +352 -0
  333. gatewaysdk/tracing/mapping.py +729 -0
  334. gatewaysdk/tracing/processors.py +393 -0
  335. gatewaysdk/tracing/push.py +617 -0
  336. gatewaysdk/tracing/push_models.py +247 -0
  337. gatewaysdk/tracing/semconv.py +294 -0
  338. gatewaysdk/tracing/span_builder.py +356 -0
  339. gatewaysdk/trainer/__init__.py +6 -0
  340. gatewaysdk/trainer/init_utils.py +263 -0
  341. gatewaysdk/trainer/legacy.py +359 -0
  342. gatewaysdk/trainer/registry.py +12 -0
  343. gatewaysdk/trainer/trainer.py +638 -0
  344. gatewaysdk/types/__init__.py +63 -0
  345. gatewaysdk/types/core.py +556 -0
  346. gatewaysdk/types/resources.py +204 -0
  347. gatewaysdk/types/tracer.py +515 -0
  348. gatewaysdk/types/tracing.py +162 -0
  349. gatewaysdk/users.py +251 -0
  350. gatewaysdk/utils/__init__.py +1 -0
  351. gatewaysdk/utils/id.py +18 -0
  352. gatewaysdk/utils/metrics.py +1025 -0
  353. gatewaysdk/utils/otel.py +550 -0
  354. gatewaysdk/utils/otlp.py +556 -0
  355. gatewaysdk/utils/redact.py +22 -0
  356. gatewaysdk/utils/server_launcher.py +1045 -0
  357. gatewaysdk/utils/system_snapshot.py +90 -0
  358. gatewaysdk/verl/__init__.py +8 -0
  359. gatewaysdk/verl/__main__.py +6 -0
  360. gatewaysdk/verl/async_server.py +46 -0
  361. gatewaysdk/verl/config.yaml +27 -0
  362. gatewaysdk/verl/daemon.py +1154 -0
  363. gatewaysdk/verl/dataset.py +44 -0
  364. gatewaysdk/verl/entrypoint.py +248 -0
  365. gatewaysdk/verl/trainer.py +549 -0
  366. gatewaysdk/world_browser.py +748 -0
  367. gatewaysdk/world_data.py +453 -0
  368. gatewaysdk/world_sessions.py +978 -0
  369. gatewaysdk/world_tasks.py +285 -0
  370. gatewaysdk/world_tools.py +115 -0
  371. gatewaysdk/worlds.py +639 -0
  372. gatewaysdk-0.3.2.dist-info/METADATA +236 -0
  373. gatewaysdk-0.3.2.dist-info/RECORD +376 -0
  374. gatewaysdk-0.3.2.dist-info/WHEEL +4 -0
  375. gatewaysdk-0.3.2.dist-info/entry_points.txt +5 -0
  376. gatewaysdk-0.3.2.dist-info/licenses/LICENSE +19 -0
@@ -0,0 +1,479 @@
1
+ """World data snapshots: the validated SQLite, built once and opened at start.
2
+
3
+ A snapshot is a ``state.sqlite`` the store wrote (contract lock and batch ledger
4
+ inside it, as always) plus a ``state.json`` sidecar naming what it holds::
5
+
6
+ {"contractHash": ..., "batchDigest": ..., "entityCounts": {...},
7
+ "builtAt": ..., "runtime": "<gatewaysdk version>", "fileSha256": ...}
8
+
9
+ Keyed by ``(contractHash, batchDigest)``: the same key is the same file, never
10
+ rebuilt. ``db/state.sqlite`` is the default snapshot (``data/initial.json``
11
+ applied, plus any ``data/batches/*.json``); ``scenarios/<name>/state.sqlite`` is
12
+ the default with that scenario's ``seed/*.json`` overlays replacing whole
13
+ entities. ``compile`` builds them, ``check`` verifies them, the served world
14
+ copies one to its temp dir and opens it instead of replaying the JSON.
15
+
16
+ Digests (the contract, section 0):
17
+
18
+ * contract hash: sha256 of the canonical contract lock, the bytes the store's
19
+ ``_check_contract`` compares;
20
+ * batch digest: ``sha256(parent + "\\n" + batchId + "\\n" + "\\n".join(sorted(chunk digests)))``,
21
+ the empty world's parent being sixty-four zeros;
22
+ * the default batch's chunks are the tree's data files (``data/initial.json``
23
+ and ``data/batches/*.json``), each digested as its bytes; a scenario's batch
24
+ is ``scenario:<name>`` over the default with its seed files as chunks.
25
+
26
+ A tree the platform published as a data-only version carries ``db/snapshot.json``,
27
+ the **pin**: the hosted snapshot's ``batchDigest`` (and sidecar fields), which
28
+ replaces the data files as the default batch. The tree's ``data/*.json`` then no
29
+ longer describes the world's data — only that snapshot does — so a pinned tree is
30
+ never rebuilt from its data files; the platform hands the file down
31
+ (``load_world_to_sandbox``, the pod's ``WORLD_SNAPSHOT_*``).
32
+
33
+ Everything here is standard library, so the api bundle's server can load it.
34
+ """
35
+
36
+ from __future__ import annotations
37
+
38
+ import gzip
39
+ import hashlib
40
+ import json
41
+ import shutil
42
+ import urllib.request
43
+ from datetime import datetime, timezone
44
+ from pathlib import Path
45
+ from typing import Any, Iterable
46
+
47
+ try:
48
+ import tomllib
49
+ except ModuleNotFoundError: # pragma: no cover - python 3.10
50
+ import tomli as tomllib
51
+
52
+ from .compiler import Contract
53
+ from .store import SQLiteStore
54
+ from .validation import ContractError, canonical, fail, read_json
55
+
56
+ SNAPSHOT = "state.sqlite"
57
+ SIDECAR = "state.json"
58
+ PIN = "snapshot.json"
59
+ DEFAULT_DIR = "db"
60
+ SCENARIOS = "scenarios"
61
+ EMPTY_DIGEST = "0" * 64
62
+ SIDECAR_KEYS = ("contractHash", "batchDigest", "entityCounts", "builtAt", "runtime", "fileSha256")
63
+ #: The hosted pod's snapshot: a presigned GET, the sha256 of the file bytes, the batch digest.
64
+ ENV_URL = "WORLD_SNAPSHOT_URL"
65
+ ENV_SHA256 = "WORLD_SNAPSHOT_SHA256"
66
+ ENV_DIGEST = "WORLD_SNAPSHOT_DIGEST"
67
+ _BLOCK = 1024 * 1024
68
+
69
+
70
+ # ---------------------------------------------------------------- digests
71
+
72
+
73
+ def contract_hash(contract: Contract) -> str:
74
+ return hashlib.sha256(canonical(contract.lock).encode("utf-8")).hexdigest()
75
+
76
+
77
+ def batch_digest(parent: str, batch_id: str, chunk_digests: Iterable[str]) -> str:
78
+ text = parent + "\n" + batch_id + "\n" + "\n".join(sorted(chunk_digests))
79
+ return hashlib.sha256(text.encode("utf-8")).hexdigest()
80
+
81
+
82
+ def file_sha256(path: str | Path) -> str:
83
+ digest = hashlib.sha256()
84
+ with open(path, "rb") as stream:
85
+ for block in iter(lambda: stream.read(_BLOCK), b""):
86
+ digest.update(block)
87
+ return digest.hexdigest()
88
+
89
+
90
+ def runtime_version() -> str:
91
+ try:
92
+ from importlib.metadata import PackageNotFoundError, version
93
+
94
+ return version("gatewaysdk")
95
+ except (ImportError, PackageNotFoundError): # the api bundle carries the package without its metadata
96
+ return "unknown"
97
+
98
+
99
+ def _data_files(world: Path) -> list[Path]:
100
+ """The default batch's chunks: the tree's data files, in the order they apply."""
101
+ files = [world / "data/initial.json"]
102
+ batches = world / "data/batches"
103
+ if batches.is_dir():
104
+ files.extend(path for path in sorted(batches.iterdir()) if path.is_file() and path.suffix == ".json")
105
+ return files
106
+
107
+
108
+ def default_batch_digest(world: str | Path) -> str:
109
+ """The digest of the tree's data files applied in order (the module header)."""
110
+ world = Path(world)
111
+ initial = read_json(world / "data/initial.json")
112
+ batch_id = initial.get("id") if isinstance(initial, dict) and isinstance(initial.get("id"), str) else ""
113
+ return batch_digest(EMPTY_DIGEST, batch_id, [file_sha256(path) for path in _data_files(world)])
114
+
115
+
116
+ def pin_path(world: str | Path) -> Path:
117
+ return Path(world) / DEFAULT_DIR / PIN
118
+
119
+
120
+ def pinned_default(world: str | Path) -> dict | None:
121
+ """The platform's pin when the tree is a data-only version: ``db/snapshot.json``
122
+ naming the hosted snapshot (``batchDigest`` at least); None for an authored tree."""
123
+ path = pin_path(world)
124
+ if not path.is_file():
125
+ return None
126
+ pin = read_json(path)
127
+ if not isinstance(pin, dict) or not isinstance(pin.get("batchDigest"), str) or len(pin["batchDigest"]) != 64:
128
+ fail(
129
+ "snapshot_pin", f"{DEFAULT_DIR}/{PIN} must name the hosted snapshot's batchDigest", f"/{DEFAULT_DIR}/{PIN}"
130
+ )
131
+ return pin
132
+
133
+
134
+ def expected_default_digest(world: str | Path) -> str:
135
+ """The batch digest the default snapshot must carry: the pin's for a data-only
136
+ version, the data files' for an authored tree."""
137
+ pin = pinned_default(world)
138
+ return pin["batchDigest"] if pin is not None else default_batch_digest(world)
139
+
140
+
141
+ def scenario_dirs(world: str | Path) -> dict[str, Path]:
142
+ """A world's authored scenarios: ``scenarios/<name>/scenario.toml``, each a starting
143
+ state (seed overlays replacing whole entities) plus an instruction."""
144
+ scenarios = Path(world) / SCENARIOS
145
+ if not scenarios.is_dir():
146
+ return {}
147
+ out: dict[str, Path] = {}
148
+ for path in sorted(scenarios.iterdir()):
149
+ toml_path = path / "scenario.toml"
150
+ if not toml_path.is_file():
151
+ continue
152
+ table = tomllib.loads(toml_path.read_text(encoding="utf-8"))
153
+ if table.get("name", path.name) != path.name:
154
+ fail(
155
+ "scenario",
156
+ f"{path.name}/scenario.toml names {table.get('name')!r}; the directory is the name",
157
+ f"/{SCENARIOS}/{path.name}",
158
+ )
159
+ for overlay in scenario_seed_files(path):
160
+ if not isinstance(read_json(overlay), dict):
161
+ fail("scenario", f"{overlay.name} must be an object of entity -> rows", f"/{SCENARIOS}/{path.name}")
162
+ out[path.name] = path
163
+ return out
164
+
165
+
166
+ def scenario_seed_files(scenario: Path) -> list[Path]:
167
+ table = tomllib.loads((scenario / "scenario.toml").read_text(encoding="utf-8"))
168
+ seed_dir = scenario / str(table.get("seed") or "seed")
169
+ return sorted(seed_dir.glob("*.json")) if seed_dir.is_dir() else []
170
+
171
+
172
+ def scenario_overlay(scenario: Path) -> dict[str, list]:
173
+ """The scenario's seed files merged: later files replace an entity named twice."""
174
+ overlay: dict[str, list] = {}
175
+ for path in scenario_seed_files(scenario):
176
+ overlay.update(read_json(path))
177
+ return overlay
178
+
179
+
180
+ def scenario_batch_digest(scenario: Path, default_digest: str) -> str:
181
+ return batch_digest(
182
+ default_digest, f"scenario:{scenario.name}", [file_sha256(p) for p in scenario_seed_files(scenario)]
183
+ )
184
+
185
+
186
+ # ---------------------------------------------------------------- sidecars
187
+
188
+
189
+ def sidecar_path(snapshot: Path) -> Path:
190
+ return snapshot.with_name(SIDECAR)
191
+
192
+
193
+ def read_sidecar(snapshot: Path) -> dict | None:
194
+ path = sidecar_path(snapshot)
195
+ if not path.is_file():
196
+ return None
197
+ try:
198
+ sidecar = json.loads(path.read_text(encoding="utf-8"))
199
+ except (OSError, ValueError) as error:
200
+ raise ContractError("snapshot", f"Cannot read the snapshot sidecar {path.name}", str(path)) from error
201
+ if not isinstance(sidecar, dict) or any(key not in sidecar for key in SIDECAR_KEYS):
202
+ fail("snapshot", f"The snapshot sidecar {path.name} lacks {', '.join(SIDECAR_KEYS)}", str(path))
203
+ return sidecar
204
+
205
+
206
+ def write_sidecar(snapshot: Path, contract_digest: str, digest: str, counts: dict[str, int]) -> dict:
207
+ sidecar = {
208
+ "contractHash": contract_digest,
209
+ "batchDigest": digest,
210
+ "entityCounts": counts,
211
+ "builtAt": datetime.now(timezone.utc).isoformat(timespec="seconds").replace("+00:00", "Z"),
212
+ "runtime": runtime_version(),
213
+ "fileSha256": file_sha256(snapshot),
214
+ }
215
+ sidecar_path(snapshot).write_text(json.dumps(sidecar, indent=1) + "\n", encoding="utf-8")
216
+ return sidecar
217
+
218
+
219
+ def current(snapshot: Path, contract_digest: str, digest: str) -> dict | None:
220
+ """The sidecar when this snapshot already is ``(contract_digest, digest)``, else None."""
221
+ if not snapshot.is_file():
222
+ return None
223
+ sidecar = read_sidecar(snapshot)
224
+ if sidecar and sidecar["contractHash"] == contract_digest and sidecar["batchDigest"] == digest:
225
+ return sidecar
226
+ return None
227
+
228
+
229
+ def verify(snapshot: Path, contract: Contract) -> dict:
230
+ """The sidecar of a snapshot whose bytes and contract it names: anything else is refused."""
231
+ sidecar = read_sidecar(snapshot)
232
+ if sidecar is None:
233
+ fail("snapshot", f"{snapshot} has no {SIDECAR} sidecar; recompile", str(snapshot))
234
+ if sidecar["contractHash"] != contract_hash(contract):
235
+ fail("snapshot", f"{snapshot} was built for a different contract; recompile", str(snapshot))
236
+ if not snapshot.is_file():
237
+ fail("snapshot", f"{snapshot} is missing; recompile", str(snapshot))
238
+ if sidecar["fileSha256"] != file_sha256(snapshot):
239
+ fail("snapshot", f"{snapshot} differs from its sidecar's fileSha256; recompile", str(snapshot))
240
+ return sidecar
241
+
242
+
243
+ # ---------------------------------------------------------------- build
244
+
245
+
246
+ def default_snapshot(world: str | Path) -> Path:
247
+ return Path(world) / DEFAULT_DIR / SNAPSHOT
248
+
249
+
250
+ def scenario_snapshot(world: str | Path, name: str) -> Path:
251
+ return Path(world) / SCENARIOS / name / SNAPSHOT
252
+
253
+
254
+ def _replace(snapshot: Path, fresh: Path) -> None:
255
+ snapshot.parent.mkdir(parents=True, exist_ok=True)
256
+ snapshot.unlink(missing_ok=True)
257
+ sidecar_path(snapshot).unlink(missing_ok=True)
258
+ shutil.move(str(fresh), str(snapshot))
259
+
260
+
261
+ def build_default(world: str | Path, contract: Contract, *, force: bool = False) -> dict:
262
+ """``db/state.sqlite`` for the tree's data files; a snapshot already at this key is
263
+ kept. A pinned tree's default is the hosted snapshot, which the data files cannot
264
+ reproduce: it is kept when present and refused when absent."""
265
+ world = Path(world)
266
+ snapshot = default_snapshot(world)
267
+ contract_digest = contract_hash(contract)
268
+ digest = expected_default_digest(world)
269
+ kept = current(snapshot, contract_digest, digest)
270
+ if pin_path(world).is_file():
271
+ if kept is None:
272
+ fail(
273
+ "snapshot_pinned",
274
+ f"{DEFAULT_DIR}/{PIN} pins the hosted snapshot {digest}, which is not in the tree: "
275
+ f"fetch the world with its snapshot (load_world_to_sandbox, gateway worlds pull) "
276
+ f"or remove the pin to rebuild from the data files",
277
+ f"/{DEFAULT_DIR}/{SNAPSHOT}",
278
+ )
279
+ return kept
280
+ if not force and kept is not None:
281
+ return kept
282
+ fresh = snapshot.with_name(f".{SNAPSHOT}.building")
283
+ fresh.parent.mkdir(parents=True, exist_ok=True)
284
+ fresh.unlink(missing_ok=True)
285
+ try:
286
+ with SQLiteStore.create(fresh, contract) as store:
287
+ for path in _data_files(world):
288
+ store.apply_batch(read_json(path))
289
+ counts = store.counts()
290
+ except BaseException:
291
+ fresh.unlink(missing_ok=True)
292
+ raise
293
+ _replace(snapshot, fresh)
294
+ return write_sidecar(snapshot, contract_digest, digest, counts)
295
+
296
+
297
+ def build_scenario(world: str | Path, name: str, contract: Contract, default: dict, *, force: bool = False) -> dict:
298
+ """``scenarios/<name>/state.sqlite``: the default snapshot with the overlays applied."""
299
+ world = Path(world)
300
+ scenario = scenario_dirs(world).get(name)
301
+ if scenario is None:
302
+ fail("scenario", f"No scenario {name}", f"/{SCENARIOS}/{name}")
303
+ snapshot = scenario_snapshot(world, name)
304
+ contract_digest = contract_hash(contract)
305
+ digest = scenario_batch_digest(scenario, default["batchDigest"])
306
+ if not force:
307
+ kept = current(snapshot, contract_digest, digest)
308
+ if kept is not None:
309
+ return kept
310
+ from .data import seed_batch
311
+
312
+ fresh = snapshot.with_name(f".{SNAPSHOT}.building")
313
+ fresh.unlink(missing_ok=True)
314
+ shutil.copyfile(default_snapshot(world), fresh)
315
+ try:
316
+ with SQLiteStore.open(fresh, contract, audit=False) as store:
317
+ overlay = scenario_overlay(scenario)
318
+ if overlay:
319
+ store.apply_batch(seed_batch(store, contract, overlay, "replace", batch_id=f"scenario:{name}"))
320
+ counts = store.counts()
321
+ except BaseException:
322
+ fresh.unlink(missing_ok=True)
323
+ raise
324
+ _replace(snapshot, fresh)
325
+ return write_sidecar(snapshot, contract_digest, digest, counts)
326
+
327
+
328
+ def build_all(world: str | Path, contract: Contract, *, force: bool = False) -> dict:
329
+ """The default snapshot and one per scenario; ``{"default": sidecar, "scenarios": {name: sidecar}}``."""
330
+ world = Path(world)
331
+ default = build_default(world, contract, force=force)
332
+ scenarios = {name: build_scenario(world, name, contract, default, force=force) for name in scenario_dirs(world)}
333
+ return {"default": default, "scenarios": scenarios}
334
+
335
+
336
+ def discard_all(world: str | Path) -> None:
337
+ """Remove every snapshot in the tree: nothing may claim data that no longer applies."""
338
+ world = Path(world)
339
+ targets = [default_snapshot(world)]
340
+ scenarios = world / SCENARIOS
341
+ if scenarios.is_dir():
342
+ targets.extend(path / SNAPSHOT for path in scenarios.iterdir() if path.is_dir())
343
+ for snapshot in targets:
344
+ snapshot.unlink(missing_ok=True)
345
+ sidecar_path(snapshot).unlink(missing_ok=True)
346
+
347
+
348
+ def check_all(world: str | Path, contract: Contract) -> dict:
349
+ """Every snapshot present in the tree, verified against its sidecar; absent ones are not required."""
350
+ world = Path(world)
351
+ found: dict[str, Any] = {"default": None, "scenarios": {}}
352
+ snapshot = default_snapshot(world)
353
+ if snapshot.is_file() or sidecar_path(snapshot).is_file():
354
+ found["default"] = verify(snapshot, contract)
355
+ # A snapshot built from earlier data than the tree holds is reported, not
356
+ # refused: authoring edits journals and opens the world, and a served world
357
+ # replays the journals past a stale snapshot (WorldState opens only a
358
+ # current one). `stale` is in every check report so it never passes unseen.
359
+ found["default"]["stale"] = (
360
+ pinned_default(world) is None and found["default"]["batchDigest"] != default_batch_digest(world)
361
+ )
362
+ for name in scenario_dirs(world):
363
+ snapshot = scenario_snapshot(world, name)
364
+ if snapshot.is_file() or sidecar_path(snapshot).is_file():
365
+ found["scenarios"][name] = verify(snapshot, contract)
366
+ return found
367
+
368
+
369
+ # ---------------------------------------------------------------- open
370
+
371
+
372
+ def usable(snapshot: Path, contract: Contract) -> dict | None:
373
+ """The sidecar when the snapshot exists and was built for this contract: what a
374
+ served world opens. A snapshot for another contract is ignored, not an error;
375
+ the legacy path (replay the JSON) still serves the world."""
376
+ if not snapshot.is_file():
377
+ return None
378
+ sidecar = read_sidecar(snapshot)
379
+ if sidecar is None or sidecar["contractHash"] != contract_hash(contract):
380
+ return None
381
+ return sidecar
382
+
383
+
384
+ def current_default(world: str | Path, contract: Contract) -> dict | None:
385
+ """The default snapshot's sidecar when it is exactly the tree's data through this
386
+ contract (what ``compile`` would skip); None when absent or stale."""
387
+ world = Path(world)
388
+ snapshot = default_snapshot(world)
389
+ if not snapshot.is_file() or not (world / "data/initial.json").is_file():
390
+ return None
391
+ return current(snapshot, contract_hash(contract), expected_default_digest(world))
392
+
393
+
394
+ def open_copy(snapshot: Path, contract: Contract, destination: Path) -> SQLiteStore:
395
+ """A session's own copy of the snapshot, opened without the row audit: the file
396
+ was audited when it was written and verified by digest since."""
397
+ destination.parent.mkdir(parents=True, exist_ok=True)
398
+ shutil.copyfile(snapshot, destination)
399
+ return SQLiteStore.open(destination, contract, audit=False)
400
+
401
+
402
+ # ---------------------------------------------------------------- hosted pod
403
+
404
+
405
+ def fetch(world: str | Path, contract: Contract, url: str, sha256: str, digest: str) -> dict:
406
+ """Download the pod's snapshot to ``db/state.sqlite``: gunzip when the URL ends in
407
+ ``.gz`` or the response says so, verify the sha256 of the file bytes, write the
408
+ sidecar. A mismatch is a refusal naming the digest; nothing usable is left behind."""
409
+ world = Path(world)
410
+ snapshot = default_snapshot(world)
411
+ snapshot.parent.mkdir(parents=True, exist_ok=True)
412
+ partial = snapshot.with_name(f".{SNAPSHOT}.download")
413
+ hasher = hashlib.sha256()
414
+ try:
415
+ with urllib.request.urlopen(urllib.request.Request(url), timeout=300) as response:
416
+ compressed = url.split("?", 1)[0].endswith(".gz") or "gzip" in (
417
+ response.headers.get("Content-Encoding") or ""
418
+ )
419
+ source = gzip.GzipFile(fileobj=response) if compressed else response
420
+ with open(partial, "wb") as out:
421
+ for block in iter(lambda: source.read(_BLOCK), b""):
422
+ hasher.update(block)
423
+ out.write(block)
424
+ except (OSError, EOFError, ValueError) as error:
425
+ partial.unlink(missing_ok=True)
426
+ raise ContractError("snapshot_download", f"Snapshot {digest} could not be downloaded: {error}") from None
427
+ if hasher.hexdigest() != sha256.lower():
428
+ partial.unlink(missing_ok=True)
429
+ fail(
430
+ "snapshot_mismatch",
431
+ f"Snapshot {digest} downloaded with sha256 {hasher.hexdigest()}, expected {sha256}",
432
+ )
433
+ try:
434
+ with SQLiteStore.open(partial, contract, audit=False) as store:
435
+ counts = store.counts()
436
+ except ContractError:
437
+ partial.unlink(missing_ok=True)
438
+ raise
439
+ _replace(snapshot, partial)
440
+ sidecar = write_sidecar(snapshot, contract_hash(contract), digest, counts)
441
+ # The hosted snapshot IS this tree's data now: pin it, so the served world opens
442
+ # it as current instead of replaying journals the snapshot supersedes.
443
+ pin_path(world).write_text(
444
+ json.dumps({"batchDigest": digest, "contractHash": contract_hash(contract), "entityCounts": counts}, indent=1)
445
+ + "\n"
446
+ )
447
+ return sidecar
448
+
449
+
450
+ __all__ = [
451
+ "EMPTY_DIGEST",
452
+ "ENV_DIGEST",
453
+ "ENV_SHA256",
454
+ "ENV_URL",
455
+ "SIDECAR",
456
+ "SNAPSHOT",
457
+ "batch_digest",
458
+ "build_all",
459
+ "build_default",
460
+ "build_scenario",
461
+ "check_all",
462
+ "contract_hash",
463
+ "current_default",
464
+ "default_batch_digest",
465
+ "default_snapshot",
466
+ "discard_all",
467
+ "expected_default_digest",
468
+ "fetch",
469
+ "file_sha256",
470
+ "open_copy",
471
+ "pinned_default",
472
+ "read_sidecar",
473
+ "scenario_dirs",
474
+ "scenario_overlay",
475
+ "scenario_snapshot",
476
+ "usable",
477
+ "verify",
478
+ "write_sidecar",
479
+ ]