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.
- gatewaysdk/__init__.py +540 -0
- gatewaysdk/adapter/__init__.py +15 -0
- gatewaysdk/adapter/base.py +94 -0
- gatewaysdk/adapter/messages.py +270 -0
- gatewaysdk/adapter/triplet.py +1026 -0
- gatewaysdk/algorithm/__init__.py +39 -0
- gatewaysdk/algorithm/apo/__init__.py +5 -0
- gatewaysdk/algorithm/apo/apo.py +898 -0
- gatewaysdk/algorithm/apo/prompts/apply_edit_variant01.poml +22 -0
- gatewaysdk/algorithm/apo/prompts/apply_edit_variant02.poml +18 -0
- gatewaysdk/algorithm/apo/prompts/text_gradient_variant01.poml +18 -0
- gatewaysdk/algorithm/apo/prompts/text_gradient_variant02.poml +16 -0
- gatewaysdk/algorithm/apo/prompts/text_gradient_variant03.poml +107 -0
- gatewaysdk/algorithm/base.py +262 -0
- gatewaysdk/algorithm/decorator.py +264 -0
- gatewaysdk/algorithm/evals/__init__.py +7 -0
- gatewaysdk/algorithm/evals/evals.py +217 -0
- gatewaysdk/algorithm/fast.py +250 -0
- gatewaysdk/algorithm/gepa/__init__.py +61 -0
- gatewaysdk/algorithm/gepa/adapter.py +495 -0
- gatewaysdk/algorithm/gepa/gepa.py +570 -0
- gatewaysdk/algorithm/gepa/lib/__init__.py +18 -0
- gatewaysdk/algorithm/gepa/lib/adapters/README.md +12 -0
- gatewaysdk/algorithm/gepa/lib/adapters/__init__.py +0 -0
- gatewaysdk/algorithm/gepa/lib/adapters/anymaths_adapter/README.md +341 -0
- gatewaysdk/algorithm/gepa/lib/adapters/anymaths_adapter/__init__.py +1 -0
- gatewaysdk/algorithm/gepa/lib/adapters/anymaths_adapter/anymaths_adapter.py +174 -0
- gatewaysdk/algorithm/gepa/lib/adapters/anymaths_adapter/requirements.txt +1 -0
- gatewaysdk/algorithm/gepa/lib/adapters/default_adapter/README.md +0 -0
- gatewaysdk/algorithm/gepa/lib/adapters/default_adapter/__init__.py +0 -0
- gatewaysdk/algorithm/gepa/lib/adapters/default_adapter/default_adapter.py +209 -0
- gatewaysdk/algorithm/gepa/lib/adapters/dspy_adapter/README.md +7 -0
- gatewaysdk/algorithm/gepa/lib/adapters/dspy_adapter/__init__.py +0 -0
- gatewaysdk/algorithm/gepa/lib/adapters/dspy_adapter/dspy_adapter.py +307 -0
- gatewaysdk/algorithm/gepa/lib/adapters/dspy_full_program_adapter/README.md +99 -0
- gatewaysdk/algorithm/gepa/lib/adapters/dspy_full_program_adapter/dspy_program_proposal_signature.py +137 -0
- gatewaysdk/algorithm/gepa/lib/adapters/dspy_full_program_adapter/full_program_adapter.py +268 -0
- gatewaysdk/algorithm/gepa/lib/adapters/generic_rag_adapter/GEPA_RAG.md +621 -0
- gatewaysdk/algorithm/gepa/lib/adapters/generic_rag_adapter/__init__.py +56 -0
- gatewaysdk/algorithm/gepa/lib/adapters/generic_rag_adapter/evaluation_metrics.py +226 -0
- gatewaysdk/algorithm/gepa/lib/adapters/generic_rag_adapter/generic_rag_adapter.py +496 -0
- gatewaysdk/algorithm/gepa/lib/adapters/generic_rag_adapter/rag_pipeline.py +238 -0
- gatewaysdk/algorithm/gepa/lib/adapters/generic_rag_adapter/vector_store_interface.py +212 -0
- gatewaysdk/algorithm/gepa/lib/adapters/generic_rag_adapter/vector_stores/__init__.py +2 -0
- gatewaysdk/algorithm/gepa/lib/adapters/generic_rag_adapter/vector_stores/chroma_store.py +196 -0
- gatewaysdk/algorithm/gepa/lib/adapters/generic_rag_adapter/vector_stores/lancedb_store.py +422 -0
- gatewaysdk/algorithm/gepa/lib/adapters/generic_rag_adapter/vector_stores/milvus_store.py +409 -0
- gatewaysdk/algorithm/gepa/lib/adapters/generic_rag_adapter/vector_stores/qdrant_store.py +368 -0
- gatewaysdk/algorithm/gepa/lib/adapters/generic_rag_adapter/vector_stores/weaviate_store.py +418 -0
- gatewaysdk/algorithm/gepa/lib/adapters/mcp_adapter/README.md +552 -0
- gatewaysdk/algorithm/gepa/lib/adapters/mcp_adapter/__init__.py +37 -0
- gatewaysdk/algorithm/gepa/lib/adapters/mcp_adapter/mcp_adapter.py +699 -0
- gatewaysdk/algorithm/gepa/lib/adapters/mcp_adapter/mcp_client.py +364 -0
- gatewaysdk/algorithm/gepa/lib/adapters/terminal_bench_adapter/README.md +9 -0
- gatewaysdk/algorithm/gepa/lib/adapters/terminal_bench_adapter/__init__.py +0 -0
- gatewaysdk/algorithm/gepa/lib/adapters/terminal_bench_adapter/terminal_bench_adapter.py +217 -0
- gatewaysdk/algorithm/gepa/lib/api.py +382 -0
- gatewaysdk/algorithm/gepa/lib/core/__init__.py +0 -0
- gatewaysdk/algorithm/gepa/lib/core/adapter.py +180 -0
- gatewaysdk/algorithm/gepa/lib/core/data_loader.py +74 -0
- gatewaysdk/algorithm/gepa/lib/core/engine.py +379 -0
- gatewaysdk/algorithm/gepa/lib/core/result.py +233 -0
- gatewaysdk/algorithm/gepa/lib/core/state.py +636 -0
- gatewaysdk/algorithm/gepa/lib/examples/__init__.py +0 -0
- gatewaysdk/algorithm/gepa/lib/examples/aime.py +24 -0
- gatewaysdk/algorithm/gepa/lib/examples/anymaths-bench/eval_default.py +111 -0
- gatewaysdk/algorithm/gepa/lib/examples/anymaths-bench/prompt-templates/instruction_prompt.txt +9 -0
- gatewaysdk/algorithm/gepa/lib/examples/anymaths-bench/prompt-templates/optimal_prompt.txt +24 -0
- gatewaysdk/algorithm/gepa/lib/examples/anymaths-bench/train_anymaths.py +177 -0
- gatewaysdk/algorithm/gepa/lib/examples/dspy_full_program_evolution/arc_agi.ipynb +25705 -0
- gatewaysdk/algorithm/gepa/lib/examples/dspy_full_program_evolution/example.ipynb +348 -0
- gatewaysdk/algorithm/gepa/lib/examples/mcp_adapter/__init__.py +4 -0
- gatewaysdk/algorithm/gepa/lib/examples/mcp_adapter/mcp_optimization_example.py +456 -0
- gatewaysdk/algorithm/gepa/lib/examples/rag_adapter/RAG_GUIDE.md +613 -0
- gatewaysdk/algorithm/gepa/lib/examples/rag_adapter/__init__.py +9 -0
- gatewaysdk/algorithm/gepa/lib/examples/rag_adapter/rag_optimization.py +820 -0
- gatewaysdk/algorithm/gepa/lib/examples/rag_adapter/requirements-rag.txt +29 -0
- gatewaysdk/algorithm/gepa/lib/examples/terminal-bench/prompt-templates/instruction_prompt.txt +16 -0
- gatewaysdk/algorithm/gepa/lib/examples/terminal-bench/prompt-templates/terminus.txt +9 -0
- gatewaysdk/algorithm/gepa/lib/examples/terminal-bench/train_terminus.py +161 -0
- gatewaysdk/algorithm/gepa/lib/gepa_utils.py +117 -0
- gatewaysdk/algorithm/gepa/lib/logging/__init__.py +0 -0
- gatewaysdk/algorithm/gepa/lib/logging/experiment_tracker.py +187 -0
- gatewaysdk/algorithm/gepa/lib/logging/logger.py +75 -0
- gatewaysdk/algorithm/gepa/lib/logging/utils.py +103 -0
- gatewaysdk/algorithm/gepa/lib/proposer/__init__.py +0 -0
- gatewaysdk/algorithm/gepa/lib/proposer/base.py +31 -0
- gatewaysdk/algorithm/gepa/lib/proposer/merge.py +357 -0
- gatewaysdk/algorithm/gepa/lib/proposer/reflective_mutation/__init__.py +0 -0
- gatewaysdk/algorithm/gepa/lib/proposer/reflective_mutation/base.py +49 -0
- gatewaysdk/algorithm/gepa/lib/proposer/reflective_mutation/reflective_mutation.py +176 -0
- gatewaysdk/algorithm/gepa/lib/py.typed +0 -0
- gatewaysdk/algorithm/gepa/lib/strategies/__init__.py +0 -0
- gatewaysdk/algorithm/gepa/lib/strategies/batch_sampler.py +77 -0
- gatewaysdk/algorithm/gepa/lib/strategies/candidate_selector.py +50 -0
- gatewaysdk/algorithm/gepa/lib/strategies/component_selector.py +36 -0
- gatewaysdk/algorithm/gepa/lib/strategies/eval_policy.py +64 -0
- gatewaysdk/algorithm/gepa/lib/strategies/instruction_proposal.py +126 -0
- gatewaysdk/algorithm/gepa/lib/utils/__init__.py +10 -0
- gatewaysdk/algorithm/gepa/lib/utils/stop_condition.py +196 -0
- gatewaysdk/algorithm/gepa/tracing.py +105 -0
- gatewaysdk/algorithm/utils.py +177 -0
- gatewaysdk/algorithm/verl/__init__.py +5 -0
- gatewaysdk/algorithm/verl/interface.py +202 -0
- gatewaysdk/automations.py +111 -0
- gatewaysdk/benchmark_hub/README.md +88 -0
- gatewaysdk/benchmark_hub/__init__.py +119 -0
- gatewaysdk/benchmark_hub/_archive.py +189 -0
- gatewaysdk/benchmark_hub/_tar.py +6 -0
- gatewaysdk/benchmark_hub/client.py +992 -0
- gatewaysdk/benchmark_hub/dispatch_shard.py +34 -0
- gatewaysdk/benchmark_hub/eval_config.py +20 -0
- gatewaysdk/benchmark_hub/evals.py +351 -0
- gatewaysdk/benchmark_hub/harbor_adapter.py +673 -0
- gatewaysdk/benchmark_hub/path_utils.py +81 -0
- gatewaysdk/benchmark_hub/save_utils.py +101 -0
- gatewaysdk/benchmark_hub/verifiers_adapter.py +327 -0
- gatewaysdk/benchmark_hub/versioning.py +72 -0
- gatewaysdk/build.py +515 -0
- gatewaysdk/cli/__init__.py +58 -0
- gatewaysdk/cli/agent_runner.py +132 -0
- gatewaysdk/cli/http_client.py +115 -0
- gatewaysdk/cli/platform.py +4086 -0
- gatewaysdk/cli/prometheus.py +115 -0
- gatewaysdk/cli/release_gate.py +215 -0
- gatewaysdk/cli/store.py +131 -0
- gatewaysdk/cli/vllm.py +29 -0
- gatewaysdk/client.py +406 -0
- gatewaysdk/config.py +348 -0
- gatewaysdk/connectors/__init__.py +25 -0
- gatewaysdk/connectors/client.py +203 -0
- gatewaysdk/connectors/skill.py +25 -0
- gatewaysdk/connectors/template.py +106 -0
- gatewaysdk/context.py +606 -0
- gatewaysdk/emitter/__init__.py +43 -0
- gatewaysdk/emitter/annotation.py +370 -0
- gatewaysdk/emitter/exception.py +54 -0
- gatewaysdk/emitter/message.py +61 -0
- gatewaysdk/emitter/object.py +117 -0
- gatewaysdk/emitter/reward.py +320 -0
- gatewaysdk/env_var.py +156 -0
- gatewaysdk/environment/__init__.py +108 -0
- gatewaysdk/environment/_bundle.py +281 -0
- gatewaysdk/environment/_harbor.py +276 -0
- gatewaysdk/environment/_materialize.py +97 -0
- gatewaysdk/environment/_tar.py +33 -0
- gatewaysdk/environment/_world.py +346 -0
- gatewaysdk/environment/core.py +527 -0
- gatewaysdk/environment/errors.py +58 -0
- gatewaysdk/environment/runtime.py +150 -0
- gatewaysdk/environment/schema/__init__.py +35 -0
- gatewaysdk/environment/schema/__main__.py +225 -0
- gatewaysdk/environment/schema/_slack_fidelity.py +116 -0
- gatewaysdk/environment/schema/_slack_scenario.py +183 -0
- gatewaysdk/environment/schema/api.py +1628 -0
- gatewaysdk/environment/schema/batch.py +755 -0
- gatewaysdk/environment/schema/compiler.py +615 -0
- gatewaysdk/environment/schema/conform.py +428 -0
- gatewaysdk/environment/schema/connector.py +777 -0
- gatewaysdk/environment/schema/connectors/apple-business-manager/handlers.py +17 -0
- gatewaysdk/environment/schema/connectors/apple-business-manager/parity.json +28 -0
- gatewaysdk/environment/schema/connectors/apple-business-manager/provenance.json +233 -0
- gatewaysdk/environment/schema/connectors/apple-business-manager/scope.toml +112 -0
- gatewaysdk/environment/schema/connectors/apple-business-manager/world.json +1380 -0
- gatewaysdk/environment/schema/connectors/base.json +28 -0
- gatewaysdk/environment/schema/connectors/custom/handlers.py +18 -0
- gatewaysdk/environment/schema/connectors/custom/world.json +123 -0
- gatewaysdk/environment/schema/connectors/github/handlers.py +81 -0
- gatewaysdk/environment/schema/connectors/github/parity.json +37 -0
- gatewaysdk/environment/schema/connectors/github/provenance.json +134 -0
- gatewaysdk/environment/schema/connectors/github/scope.toml +158 -0
- gatewaysdk/environment/schema/connectors/github/world.json +5869 -0
- gatewaysdk/environment/schema/connectors/google-calendar/handlers.py +23 -0
- gatewaysdk/environment/schema/connectors/google-calendar/world.json +209 -0
- gatewaysdk/environment/schema/connectors/google-drive/handlers.py +18 -0
- gatewaysdk/environment/schema/connectors/google-drive/world.json +286 -0
- gatewaysdk/environment/schema/connectors/jamf/handlers.py +34 -0
- gatewaysdk/environment/schema/connectors/jamf/parity.json +49 -0
- gatewaysdk/environment/schema/connectors/jamf/provenance.json +181 -0
- gatewaysdk/environment/schema/connectors/jamf/scope.toml +128 -0
- gatewaysdk/environment/schema/connectors/jamf/world.json +2218 -0
- gatewaysdk/environment/schema/connectors/jira/handlers.py +28 -0
- gatewaysdk/environment/schema/connectors/jira/world.json +389 -0
- gatewaysdk/environment/schema/connectors/kandji/handlers.py +38 -0
- gatewaysdk/environment/schema/connectors/kandji/parity.json +29 -0
- gatewaysdk/environment/schema/connectors/kandji/provenance.json +152 -0
- gatewaysdk/environment/schema/connectors/kandji/scope.toml +120 -0
- gatewaysdk/environment/schema/connectors/kandji/world.json +765 -0
- gatewaysdk/environment/schema/connectors/linear/handlers.py +20 -0
- gatewaysdk/environment/schema/connectors/linear/world.json +363 -0
- gatewaysdk/environment/schema/connectors/microsoft-teams/fidelity.json +45 -0
- gatewaysdk/environment/schema/connectors/microsoft-teams/handlers.py +1 -0
- gatewaysdk/environment/schema/connectors/microsoft-teams/provenance.json +851 -0
- gatewaysdk/environment/schema/connectors/microsoft-teams/scope.md +231 -0
- gatewaysdk/environment/schema/connectors/microsoft-teams/teams-types.json +1710 -0
- gatewaysdk/environment/schema/connectors/microsoft-teams/world.json +858 -0
- gatewaysdk/environment/schema/connectors/netsuite/handlers.py +32 -0
- gatewaysdk/environment/schema/connectors/netsuite/parity.json +50 -0
- gatewaysdk/environment/schema/connectors/netsuite/provenance.json +137 -0
- gatewaysdk/environment/schema/connectors/netsuite/scope.toml +78 -0
- gatewaysdk/environment/schema/connectors/netsuite/world.json +16243 -0
- gatewaysdk/environment/schema/connectors/salesforce/handlers.py +806 -0
- gatewaysdk/environment/schema/connectors/salesforce/parity.json +46 -0
- gatewaysdk/environment/schema/connectors/salesforce/provenance.json +143 -0
- gatewaysdk/environment/schema/connectors/salesforce/scope.toml +174 -0
- gatewaysdk/environment/schema/connectors/salesforce/world.json +4695 -0
- gatewaysdk/environment/schema/connectors/slack/MODEL.md +236 -0
- gatewaysdk/environment/schema/connectors/slack/capabilities.json +1039 -0
- gatewaysdk/environment/schema/connectors/slack/handlers.py +762 -0
- gatewaysdk/environment/schema/connectors/slack/parity.json +65 -0
- gatewaysdk/environment/schema/connectors/slack/provenance.json +294 -0
- gatewaysdk/environment/schema/connectors/slack/scope.toml +44 -0
- gatewaysdk/environment/schema/connectors/slack/world.json +4993 -0
- gatewaysdk/environment/schema/connectors/workday/handlers.py +75 -0
- gatewaysdk/environment/schema/connectors/workday/parity.json +36 -0
- gatewaysdk/environment/schema/connectors/workday/provenance.json +179 -0
- gatewaysdk/environment/schema/connectors/workday/scope.toml +180 -0
- gatewaysdk/environment/schema/connectors/workday/world.json +806 -0
- gatewaysdk/environment/schema/data.py +593 -0
- gatewaysdk/environment/schema/extract.py +263 -0
- gatewaysdk/environment/schema/host.py +2437 -0
- gatewaysdk/environment/schema/host_surfaces.py +275 -0
- gatewaysdk/environment/schema/platform.py +1272 -0
- gatewaysdk/environment/schema/scaffold.py +250 -0
- gatewaysdk/environment/schema/skill.py +118 -0
- gatewaysdk/environment/schema/skills/agents/connector-template-author.md +202 -0
- gatewaysdk/environment/schema/skills/connector-schema-authoring/CONNECTORS.md +331 -0
- gatewaysdk/environment/schema/skills/connector-schema-authoring/CONVENTIONS.md +100 -0
- gatewaysdk/environment/schema/skills/connector-schema-authoring/SKILL.md +313 -0
- gatewaysdk/environment/schema/skills/world-data-ingestion/ROWS.md +223 -0
- gatewaysdk/environment/schema/skills/world-data-ingestion/SKILL.md +293 -0
- gatewaysdk/environment/schema/skills/worlds-getting-started/SKILL.md +390 -0
- gatewaysdk/environment/schema/slack.py +757 -0
- gatewaysdk/environment/schema/snapshot.py +479 -0
- gatewaysdk/environment/schema/store.py +904 -0
- gatewaysdk/environment/schema/tasks.py +525 -0
- gatewaysdk/environment/schema/tools_world.py +322 -0
- gatewaysdk/environment/schema/validation.py +577 -0
- gatewaysdk/environment/schema/worker.py +78 -0
- gatewaysdk/environment/schema/workers.py +500 -0
- gatewaysdk/environment/schema/world_tests.py +336 -0
- gatewaysdk/environments/__init__.py +24 -0
- gatewaysdk/environments/client.py +486 -0
- gatewaysdk/environments/types.py +318 -0
- gatewaysdk/execution/__init__.py +15 -0
- gatewaysdk/execution/base.py +64 -0
- gatewaysdk/execution/client_server.py +443 -0
- gatewaysdk/execution/events.py +69 -0
- gatewaysdk/execution/inter_process.py +16 -0
- gatewaysdk/execution/shared_memory.py +282 -0
- gatewaysdk/experiments/__init__.py +90 -0
- gatewaysdk/experiments/assignment.py +177 -0
- gatewaysdk/experiments/client.py +877 -0
- gatewaysdk/experiments/exposure.py +222 -0
- gatewaysdk/experiments/types.py +81 -0
- gatewaysdk/importers/__init__.py +49 -0
- gatewaysdk/importers/_normalize.py +91 -0
- gatewaysdk/importers/builder.py +173 -0
- gatewaysdk/importers/client.py +95 -0
- gatewaysdk/importers/langsmith.py +196 -0
- gatewaysdk/importers/recipes.py +249 -0
- gatewaysdk/instrumentation/__init__.py +300 -0
- gatewaysdk/instrumentation/agentops.py +314 -0
- gatewaysdk/instrumentation/agentops_langchain.py +45 -0
- gatewaysdk/instrumentation/base.py +119 -0
- gatewaysdk/instrumentation/litellm.py +83 -0
- gatewaysdk/instrumentation/registry.py +273 -0
- gatewaysdk/instrumentation/vllm.py +81 -0
- gatewaysdk/instrumentation/weave.py +500 -0
- gatewaysdk/integrations/__init__.py +15 -0
- gatewaysdk/integrations/gateway/__init__.py +11 -0
- gatewaysdk/integrations/gateway/client.py +171 -0
- gatewaysdk/integrations/tool_access.py +549 -0
- gatewaysdk/litagent/__init__.py +11 -0
- gatewaysdk/litagent/decorator.py +536 -0
- gatewaysdk/litagent/litagent.py +252 -0
- gatewaysdk/llm_proxy.py +1742 -0
- gatewaysdk/logging.py +370 -0
- gatewaysdk/memory.py +278 -0
- gatewaysdk/personas.py +186 -0
- gatewaysdk/platform/__init__.py +17 -0
- gatewaysdk/platform/builder.py +221 -0
- gatewaysdk/platform/compatibility.py +67 -0
- gatewaysdk/platform/manifest.py +185 -0
- gatewaysdk/platform/orchestrator.py +901 -0
- gatewaysdk/platform/registry.py +122 -0
- gatewaysdk/platform/worker.py +864 -0
- gatewaysdk/replay/__init__.py +1032 -0
- gatewaysdk/replay/pytest.py +56 -0
- gatewaysdk/reward.py +7 -0
- gatewaysdk/run.py +1781 -0
- gatewaysdk/runner/__init__.py +11 -0
- gatewaysdk/runner/agent.py +878 -0
- gatewaysdk/runner/base.py +182 -0
- gatewaysdk/runner/legacy.py +309 -0
- gatewaysdk/security.py +700 -0
- gatewaysdk/semconv.py +170 -0
- gatewaysdk/server.py +399 -0
- gatewaysdk/sessions.py +282 -0
- gatewaysdk/store/__init__.py +45 -0
- gatewaysdk/store/base.py +908 -0
- gatewaysdk/store/client_server.py +2093 -0
- gatewaysdk/store/collection/__init__.py +30 -0
- gatewaysdk/store/collection/base.py +587 -0
- gatewaysdk/store/collection/memory.py +970 -0
- gatewaysdk/store/collection/mongo.py +1412 -0
- gatewaysdk/store/collection_based.py +1823 -0
- gatewaysdk/store/gateway.py +983 -0
- gatewaysdk/store/gateway_listener.py +465 -0
- gatewaysdk/store/listener.py +58 -0
- gatewaysdk/store/memory.py +396 -0
- gatewaysdk/store/mongo.py +165 -0
- gatewaysdk/store/redis_stream.py +517 -0
- gatewaysdk/store/sqlite.py +3 -0
- gatewaysdk/store/threading.py +370 -0
- gatewaysdk/store/utils.py +142 -0
- gatewaysdk/tracer/__init__.py +14 -0
- gatewaysdk/tracer/base.py +286 -0
- gatewaysdk/tracer/dummy.py +106 -0
- gatewaysdk/tracer/otel.py +559 -0
- gatewaysdk/tracing/__init__.py +110 -0
- gatewaysdk/tracing/api.py +808 -0
- gatewaysdk/tracing/attributes.py +9 -0
- gatewaysdk/tracing/context.py +272 -0
- gatewaysdk/tracing/exporters/__init__.py +10 -0
- gatewaysdk/tracing/exporters/gateway.py +228 -0
- gatewaysdk/tracing/identity.py +288 -0
- gatewaysdk/tracing/init.py +620 -0
- gatewaysdk/tracing/instrumentors/__init__.py +15 -0
- gatewaysdk/tracing/instrumentors/claude_agent_sdk.py +766 -0
- gatewaysdk/tracing/instrumentors/instrumentation_principles.md +294 -0
- gatewaysdk/tracing/instrumentors/registry.py +352 -0
- gatewaysdk/tracing/mapping.py +729 -0
- gatewaysdk/tracing/processors.py +393 -0
- gatewaysdk/tracing/push.py +617 -0
- gatewaysdk/tracing/push_models.py +247 -0
- gatewaysdk/tracing/semconv.py +294 -0
- gatewaysdk/tracing/span_builder.py +356 -0
- gatewaysdk/trainer/__init__.py +6 -0
- gatewaysdk/trainer/init_utils.py +263 -0
- gatewaysdk/trainer/legacy.py +359 -0
- gatewaysdk/trainer/registry.py +12 -0
- gatewaysdk/trainer/trainer.py +638 -0
- gatewaysdk/types/__init__.py +63 -0
- gatewaysdk/types/core.py +556 -0
- gatewaysdk/types/resources.py +204 -0
- gatewaysdk/types/tracer.py +515 -0
- gatewaysdk/types/tracing.py +162 -0
- gatewaysdk/users.py +251 -0
- gatewaysdk/utils/__init__.py +1 -0
- gatewaysdk/utils/id.py +18 -0
- gatewaysdk/utils/metrics.py +1025 -0
- gatewaysdk/utils/otel.py +550 -0
- gatewaysdk/utils/otlp.py +556 -0
- gatewaysdk/utils/redact.py +22 -0
- gatewaysdk/utils/server_launcher.py +1045 -0
- gatewaysdk/utils/system_snapshot.py +90 -0
- gatewaysdk/verl/__init__.py +8 -0
- gatewaysdk/verl/__main__.py +6 -0
- gatewaysdk/verl/async_server.py +46 -0
- gatewaysdk/verl/config.yaml +27 -0
- gatewaysdk/verl/daemon.py +1154 -0
- gatewaysdk/verl/dataset.py +44 -0
- gatewaysdk/verl/entrypoint.py +248 -0
- gatewaysdk/verl/trainer.py +549 -0
- gatewaysdk/world_browser.py +748 -0
- gatewaysdk/world_data.py +453 -0
- gatewaysdk/world_sessions.py +978 -0
- gatewaysdk/world_tasks.py +285 -0
- gatewaysdk/world_tools.py +115 -0
- gatewaysdk/worlds.py +639 -0
- gatewaysdk-0.3.2.dist-info/METADATA +236 -0
- gatewaysdk-0.3.2.dist-info/RECORD +376 -0
- gatewaysdk-0.3.2.dist-info/WHEEL +4 -0
- gatewaysdk-0.3.2.dist-info/entry_points.txt +5 -0
- 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
|
+
]
|