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,757 @@
|
|
|
1
|
+
"""Deterministic Slack evidence mapping and executable bundle construction.
|
|
2
|
+
|
|
3
|
+
Capture owns authorization and verified evidence bytes. This module never contacts
|
|
4
|
+
Slack, reads credentials, downloads files, guesses missing targets, or prunes rows.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from copy import deepcopy
|
|
10
|
+
import hashlib
|
|
11
|
+
import json
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
import shutil
|
|
14
|
+
import tempfile
|
|
15
|
+
|
|
16
|
+
from .compiler import Contract, check_bundle
|
|
17
|
+
from .scaffold import init_bundle
|
|
18
|
+
from .store import SQLiteStore
|
|
19
|
+
from .validation import encoded, fail, read_json
|
|
20
|
+
from ._slack_fidelity import fidelity_report, load_capabilities, slack_template_digest
|
|
21
|
+
from ._slack_scenario import write_scenario
|
|
22
|
+
|
|
23
|
+
FORMAT = "gateway-slack-evidence/1"
|
|
24
|
+
MAPPING = {"id": "slack-evidence", "version": "1"}
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _merge(old, patch):
|
|
28
|
+
if isinstance(old, dict) and isinstance(patch, dict):
|
|
29
|
+
return {**deepcopy(old), **{k: _merge(old.get(k), v) for k, v in patch.items()}}
|
|
30
|
+
return deepcopy(patch)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _identity(contract, entity, value):
|
|
34
|
+
try:
|
|
35
|
+
return {field: value[field] for field in contract.entities[entity]["primary_key"]}
|
|
36
|
+
except (KeyError, TypeError):
|
|
37
|
+
fail("slack_evidence", "Observed record is missing a natural key", "/evidence", entity=entity)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _envelope(evidence):
|
|
41
|
+
if not isinstance(evidence, dict) or evidence.get("format") != FORMAT:
|
|
42
|
+
fail("slack_evidence", "Expected gateway-slack-evidence/1", "/evidence/format")
|
|
43
|
+
workspace = evidence.get("workspace")
|
|
44
|
+
principal = evidence.get("principal")
|
|
45
|
+
if not isinstance(workspace, dict) or not isinstance(workspace.get("id"), str) or not workspace["id"]:
|
|
46
|
+
fail("slack_evidence", "A captured workspace identity is required", "/evidence/workspace")
|
|
47
|
+
if not isinstance(principal, dict) or not isinstance(principal.get("user_id"), str) or not principal["user_id"]:
|
|
48
|
+
fail("slack_evidence", "A captured principal identity is required", "/evidence/principal")
|
|
49
|
+
if not isinstance(evidence.get("observations"), list):
|
|
50
|
+
fail("slack_evidence", "Observations must be an array", "/evidence/observations")
|
|
51
|
+
return workspace, principal
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def map_slack_evidence(
|
|
55
|
+
evidence: dict, contract: Contract, *, existing=None, batch_id: str | None = None, diagnostics: dict | None = None
|
|
56
|
+
) -> dict:
|
|
57
|
+
"""Map safe Slack response projections to one atomic batch with explicit patches.
|
|
58
|
+
|
|
59
|
+
``existing`` is an optional validated store used when refreshing. Missing
|
|
60
|
+
properties are untouched; explicit nulls/empty collections remain observed
|
|
61
|
+
values. Partial pages never imply deletions. All closure is checked by store.
|
|
62
|
+
Sources must refer to capture-owned, digest-verified bytes (or synthetic bytes).
|
|
63
|
+
"""
|
|
64
|
+
workspace, principal = _envelope(evidence)
|
|
65
|
+
wid = workspace["id"]
|
|
66
|
+
records: dict[tuple[str, bytes], dict] = {}
|
|
67
|
+
deletions = []
|
|
68
|
+
removals = {}
|
|
69
|
+
diagnostics = diagnostics if diagnostics is not None else {}
|
|
70
|
+
diagnostics.setdefault("unmapped_fields", [])
|
|
71
|
+
|
|
72
|
+
def remove_observed_collection(entity, where, retained, source):
|
|
73
|
+
"""Explicit nested collection values can clear their own keyed rows.
|
|
74
|
+
|
|
75
|
+
This is distinct from absent data or a partial top-level API page. The
|
|
76
|
+
caller names each exact association collection represented by the value.
|
|
77
|
+
"""
|
|
78
|
+
if existing is None:
|
|
79
|
+
return
|
|
80
|
+
for row in existing.list(entity):
|
|
81
|
+
if row.get("workspace_id") != wid or any(row.get(k) != v for k, v in where.items()):
|
|
82
|
+
continue
|
|
83
|
+
key = _identity(contract, entity, row)
|
|
84
|
+
token = entity, encoded(key)
|
|
85
|
+
if token not in retained:
|
|
86
|
+
removals[token] = {
|
|
87
|
+
"op": "delete",
|
|
88
|
+
"entity": entity,
|
|
89
|
+
"key": key,
|
|
90
|
+
"source": {
|
|
91
|
+
"record": "slack:observed-collection:" + hashlib.sha256(encoded(key)).hexdigest(),
|
|
92
|
+
"evidence": [source],
|
|
93
|
+
},
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
def project(entity, value):
|
|
97
|
+
if not isinstance(value, dict):
|
|
98
|
+
fail("slack_evidence", "Expected an object observation", "/evidence", entity=entity)
|
|
99
|
+
allowed = contract.entities[entity]["schema"]["properties"]
|
|
100
|
+
normalized = {
|
|
101
|
+
"slack_users": {"presence"},
|
|
102
|
+
"slack_channels": {
|
|
103
|
+
"is_member",
|
|
104
|
+
"is_open",
|
|
105
|
+
"is_starred",
|
|
106
|
+
"is_read_only",
|
|
107
|
+
"is_thread_only",
|
|
108
|
+
"user",
|
|
109
|
+
"last_read",
|
|
110
|
+
"unread_count",
|
|
111
|
+
"unread_count_display",
|
|
112
|
+
"latest",
|
|
113
|
+
},
|
|
114
|
+
"slack_messages": {"thread_ts", "reactions", "files", "is_starred"},
|
|
115
|
+
"slack_files": {"shares", "channels", "groups", "ims"},
|
|
116
|
+
"slack_file_shares": {"ts", "thread_ts", "position"},
|
|
117
|
+
"slack_presence": {"ok"},
|
|
118
|
+
"slack_usergroups": {"users"},
|
|
119
|
+
}
|
|
120
|
+
for field in sorted(set(value) - set(allowed) - normalized.get(entity, set())):
|
|
121
|
+
marker = entity + "." + field
|
|
122
|
+
if marker not in diagnostics["unmapped_fields"]:
|
|
123
|
+
diagnostics["unmapped_fields"].append(marker)
|
|
124
|
+
return {k: deepcopy(v) for k, v in value.items() if k in allowed}
|
|
125
|
+
|
|
126
|
+
def observe(entity, value, source):
|
|
127
|
+
value = project(entity, value)
|
|
128
|
+
key = _identity(contract, entity, value)
|
|
129
|
+
token = entity, encoded(key)
|
|
130
|
+
if not isinstance(source, dict) or set(source) != {"reference", "digest"}:
|
|
131
|
+
fail("slack_evidence", "Each observation requires an evidence reference and digest", "/evidence/source")
|
|
132
|
+
if token not in records:
|
|
133
|
+
records[token] = {"entity": entity, "key": key, "value": value, "evidence": []}
|
|
134
|
+
else:
|
|
135
|
+
records[token]["value"] = _merge(records[token]["value"], value)
|
|
136
|
+
if source not in records[token]["evidence"]:
|
|
137
|
+
records[token]["evidence"].append(deepcopy(source))
|
|
138
|
+
|
|
139
|
+
# The containing workspace is provider-supplied context, never an actor's home team.
|
|
140
|
+
root_source = evidence.get("source")
|
|
141
|
+
if not root_source and evidence["observations"]:
|
|
142
|
+
root_source = evidence["observations"][0].get("source")
|
|
143
|
+
if not root_source:
|
|
144
|
+
fail("slack_evidence", "Evidence source metadata is required", "/evidence/source")
|
|
145
|
+
observe("slack_workspaces", workspace, root_source)
|
|
146
|
+
|
|
147
|
+
def user(value, source):
|
|
148
|
+
normalized = {**value, "workspace_id": wid}
|
|
149
|
+
profile = normalized.get("profile")
|
|
150
|
+
if isinstance(profile, dict) and "fields" in profile:
|
|
151
|
+
profile = dict(profile)
|
|
152
|
+
fields = profile.pop("fields")
|
|
153
|
+
profile["fields_state"] = "null" if fields is None else "present"
|
|
154
|
+
normalized["profile"] = profile
|
|
155
|
+
if fields is not None:
|
|
156
|
+
if not isinstance(fields, dict):
|
|
157
|
+
fail("slack_evidence", "Profile fields must be an object or null", "/evidence/profile/fields")
|
|
158
|
+
for field_id, field in fields.items():
|
|
159
|
+
observe(
|
|
160
|
+
"slack_user_profile_fields",
|
|
161
|
+
{"workspace_id": wid, "user_id": value["id"], "field_id": field_id, **field},
|
|
162
|
+
source,
|
|
163
|
+
)
|
|
164
|
+
retained = {
|
|
165
|
+
("slack_user_profile_fields", encoded({"workspace_id": wid, "user_id": value["id"], "field_id": fid}))
|
|
166
|
+
for fid in (fields or {})
|
|
167
|
+
}
|
|
168
|
+
remove_observed_collection("slack_user_profile_fields", {"user_id": value["id"]}, retained, source)
|
|
169
|
+
observe("slack_users", normalized, source)
|
|
170
|
+
|
|
171
|
+
def channel(value, source):
|
|
172
|
+
observe("slack_channels", {**value, "workspace_id": wid}, source)
|
|
173
|
+
state = {"workspace_id": wid, "channel_id": value["id"], "user_id": principal["user_id"]}
|
|
174
|
+
fields = contract.entities["slack_conversation_states"]["schema"]["properties"]
|
|
175
|
+
for k in fields:
|
|
176
|
+
if k in value and k not in ("workspace_id", "channel_id", "user_id"):
|
|
177
|
+
state[k] = value[k]
|
|
178
|
+
if "user" in value:
|
|
179
|
+
state["counterpart_user_id"] = value["user"]
|
|
180
|
+
if len(state) > 3:
|
|
181
|
+
observe("slack_conversation_states", state, source)
|
|
182
|
+
if value.get("is_member") is True:
|
|
183
|
+
observe(
|
|
184
|
+
"slack_memberships",
|
|
185
|
+
{"workspace_id": wid, "channel_id": value["id"], "user_id": principal["user_id"]},
|
|
186
|
+
source,
|
|
187
|
+
)
|
|
188
|
+
elif value.get("is_member") is False:
|
|
189
|
+
remove_observed_collection(
|
|
190
|
+
"slack_memberships", {"channel_id": value["id"], "user_id": principal["user_id"]}, set(), source
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
def file_record(value, source):
|
|
194
|
+
if (
|
|
195
|
+
not all(k in value for k in ("id", "mode", "mimetype", "size"))
|
|
196
|
+
or value.get("mode") in ("tombstone", "file_access")
|
|
197
|
+
or value.get("file_access") == "check_file_info"
|
|
198
|
+
):
|
|
199
|
+
return False
|
|
200
|
+
observe("slack_files", {**value, "workspace_id": wid}, source)
|
|
201
|
+
for name in ("channels", "groups", "ims"):
|
|
202
|
+
for cid in value.get(name, []):
|
|
203
|
+
observe(
|
|
204
|
+
"slack_file_channel_observations",
|
|
205
|
+
{"workspace_id": wid, "file_id": value["id"], "channel_id": cid, "kind": name},
|
|
206
|
+
source,
|
|
207
|
+
)
|
|
208
|
+
return True
|
|
209
|
+
|
|
210
|
+
def message(value, cid, source):
|
|
211
|
+
if not cid:
|
|
212
|
+
fail("slack_evidence", "Message observation requires channel context", "/evidence/context/channel_id")
|
|
213
|
+
observe("slack_messages", {**value, "workspace_id": wid, "channel_id": cid}, source)
|
|
214
|
+
ts = value["ts"]
|
|
215
|
+
if value.get("thread_ts") and value["thread_ts"] != ts:
|
|
216
|
+
observe(
|
|
217
|
+
"slack_message_threads",
|
|
218
|
+
{"workspace_id": wid, "channel_id": cid, "reply_ts": ts, "parent_ts": value["thread_ts"]},
|
|
219
|
+
source,
|
|
220
|
+
)
|
|
221
|
+
for reaction in value.get("reactions", []):
|
|
222
|
+
base = {"workspace_id": wid, "channel_id": cid, "message_ts": ts, "name": reaction["name"]}
|
|
223
|
+
observe("slack_reaction_summaries", {**base, "count": reaction["count"]}, source)
|
|
224
|
+
for actor in reaction.get("users", []):
|
|
225
|
+
observe("slack_reaction_users", {**base, "user_id": actor}, source)
|
|
226
|
+
if "reactions" in value and not value["reactions"]:
|
|
227
|
+
for entity in ("slack_reaction_users", "slack_reaction_summaries"):
|
|
228
|
+
remove_observed_collection(entity, {"channel_id": cid, "message_ts": ts}, set(), source)
|
|
229
|
+
for position, item in enumerate(value.get("files", [])):
|
|
230
|
+
base = {
|
|
231
|
+
"workspace_id": wid,
|
|
232
|
+
"channel_id": cid,
|
|
233
|
+
"message_ts": ts,
|
|
234
|
+
"file_id": item["id"],
|
|
235
|
+
"position": position,
|
|
236
|
+
}
|
|
237
|
+
if file_record(item, source):
|
|
238
|
+
observe("slack_message_files", base, source)
|
|
239
|
+
for visibility, channels in item.get("shares", {}).items():
|
|
240
|
+
for share_channel, shares in channels.items():
|
|
241
|
+
for share in shares:
|
|
242
|
+
# Share metadata has structural meaning only for this observed occurrence.
|
|
243
|
+
if share_channel == cid and share.get("ts") == ts:
|
|
244
|
+
observe(
|
|
245
|
+
"slack_file_shares",
|
|
246
|
+
(
|
|
247
|
+
{
|
|
248
|
+
**base,
|
|
249
|
+
**share,
|
|
250
|
+
"visibility": visibility,
|
|
251
|
+
"observed_thread_ts": share.get("thread_ts"),
|
|
252
|
+
}
|
|
253
|
+
if "thread_ts" in share
|
|
254
|
+
else {**base, **share, "visibility": visibility}
|
|
255
|
+
),
|
|
256
|
+
source,
|
|
257
|
+
)
|
|
258
|
+
else:
|
|
259
|
+
discriminator = {k: item[k] for k in ("file_access", "mode") if k in item}
|
|
260
|
+
if item.get("file_access") == "check_file_info":
|
|
261
|
+
discriminator = {"file_access": "check_file_info"}
|
|
262
|
+
if not discriminator:
|
|
263
|
+
# No canonical full shape or explicit unavailable discriminator: refuse invention.
|
|
264
|
+
fail(
|
|
265
|
+
"slack_evidence", "Incomplete file projection lacks an access discriminator", "/evidence/files"
|
|
266
|
+
)
|
|
267
|
+
observe("slack_unavailable_message_files", {**base, "reference": discriminator}, source)
|
|
268
|
+
if "files" in value:
|
|
269
|
+
# A message's explicitly supplied attachment collection is one value,
|
|
270
|
+
# unlike an API page of messages. Retain file metadata as historical
|
|
271
|
+
# observations but replace the occurrence's full/unavailable links.
|
|
272
|
+
for entity in ("slack_file_shares", "slack_message_files", "slack_unavailable_message_files"):
|
|
273
|
+
retained = {
|
|
274
|
+
token
|
|
275
|
+
for token, record in records.items()
|
|
276
|
+
if token[0] == entity
|
|
277
|
+
and record["value"].get("channel_id") == cid
|
|
278
|
+
and record["value"].get("message_ts") == ts
|
|
279
|
+
}
|
|
280
|
+
if entity == "slack_file_shares":
|
|
281
|
+
retained |= {
|
|
282
|
+
("slack_file_shares", token[1])
|
|
283
|
+
for token, record in records.items()
|
|
284
|
+
if token[0] == "slack_message_files"
|
|
285
|
+
and record["value"].get("channel_id") == cid
|
|
286
|
+
and record["value"].get("message_ts") == ts
|
|
287
|
+
}
|
|
288
|
+
remove_observed_collection(entity, {"channel_id": cid, "message_ts": ts}, retained, source)
|
|
289
|
+
if "is_starred" in value:
|
|
290
|
+
observe(
|
|
291
|
+
"slack_message_user_states",
|
|
292
|
+
{
|
|
293
|
+
"workspace_id": wid,
|
|
294
|
+
"channel_id": cid,
|
|
295
|
+
"message_ts": ts,
|
|
296
|
+
"user_id": principal["user_id"],
|
|
297
|
+
"is_starred": value["is_starred"],
|
|
298
|
+
},
|
|
299
|
+
source,
|
|
300
|
+
)
|
|
301
|
+
|
|
302
|
+
for observation in evidence["observations"]:
|
|
303
|
+
if not isinstance(observation, dict):
|
|
304
|
+
fail("slack_evidence", "Observation must be an object", "/evidence/observations")
|
|
305
|
+
method = observation.get("method")
|
|
306
|
+
context = observation.get("context", {})
|
|
307
|
+
data = observation.get("data")
|
|
308
|
+
source = observation.get("source", evidence.get("source"))
|
|
309
|
+
if not isinstance(data, dict) or data.get("ok") is not True:
|
|
310
|
+
fail(
|
|
311
|
+
"slack_evidence", "Only successful projected observations can be mapped", "/evidence/observations/data"
|
|
312
|
+
)
|
|
313
|
+
cid = context.get("channel_id")
|
|
314
|
+
if method == "auth.test":
|
|
315
|
+
if data.get("team_id") != wid or data.get("user_id") != principal["user_id"]:
|
|
316
|
+
fail("slack_evidence", "Capture principal/workspace identity mismatch", "/evidence/observations/data")
|
|
317
|
+
elif method == "team.info":
|
|
318
|
+
if data["team"]["id"] != wid:
|
|
319
|
+
fail("slack_evidence", "Workspace observation identity mismatch", "/evidence/observations/data")
|
|
320
|
+
observe("slack_workspaces", data["team"], source)
|
|
321
|
+
elif method == "users.list":
|
|
322
|
+
for value in data["members"]:
|
|
323
|
+
user(value, source)
|
|
324
|
+
elif method == "users.info":
|
|
325
|
+
user(data["user"], source)
|
|
326
|
+
elif method == "conversations.list":
|
|
327
|
+
for value in data["channels"]:
|
|
328
|
+
channel(value, source)
|
|
329
|
+
elif method == "conversations.info":
|
|
330
|
+
channel(data["channel"], source)
|
|
331
|
+
elif method == "conversations.members":
|
|
332
|
+
for actor in data["members"]:
|
|
333
|
+
observe("slack_memberships", {"workspace_id": wid, "channel_id": cid, "user_id": actor}, source)
|
|
334
|
+
elif method in ("conversations.history", "conversations.replies"):
|
|
335
|
+
for value in data["messages"]:
|
|
336
|
+
message(value, cid, source)
|
|
337
|
+
elif method == "files.info":
|
|
338
|
+
if not file_record(data["file"], source):
|
|
339
|
+
fail("slack_evidence", "Standalone incomplete file cannot form a canonical file row", "/evidence/files")
|
|
340
|
+
elif method == "pins.list":
|
|
341
|
+
for item in data["items"]:
|
|
342
|
+
if item.get("type") != "message":
|
|
343
|
+
continue
|
|
344
|
+
message(item["message"], cid, source)
|
|
345
|
+
observe(
|
|
346
|
+
"slack_pins",
|
|
347
|
+
{
|
|
348
|
+
"workspace_id": wid,
|
|
349
|
+
"channel_id": cid,
|
|
350
|
+
"message_ts": item["message"]["ts"],
|
|
351
|
+
**{k: item[k] for k in ("created", "created_by") if k in item},
|
|
352
|
+
},
|
|
353
|
+
source,
|
|
354
|
+
)
|
|
355
|
+
elif method == "bookmarks.list":
|
|
356
|
+
for value in data["bookmarks"]:
|
|
357
|
+
observe("slack_bookmarks", {**value, "workspace_id": wid, "channel_id": cid}, source)
|
|
358
|
+
elif method == "users.getPresence":
|
|
359
|
+
observe("slack_presence", {**data, "workspace_id": wid, "user_id": context["user_id"]}, source)
|
|
360
|
+
elif method == "usergroups.list":
|
|
361
|
+
for value in data["usergroups"]:
|
|
362
|
+
observe(
|
|
363
|
+
"slack_usergroups",
|
|
364
|
+
{**value, "workspace_id": wid, **({"users_state": "present"} if "users" in value else {})},
|
|
365
|
+
source,
|
|
366
|
+
)
|
|
367
|
+
for actor in value.get("users", []):
|
|
368
|
+
observe(
|
|
369
|
+
"slack_usergroup_memberships",
|
|
370
|
+
{"workspace_id": wid, "usergroup_id": value["id"], "user_id": actor},
|
|
371
|
+
source,
|
|
372
|
+
)
|
|
373
|
+
elif method == "message.deleted":
|
|
374
|
+
# Only explicit captured deletion events authorize deletion. History
|
|
375
|
+
# absence, access loss and complete-page claims never enter this path.
|
|
376
|
+
if existing is None or not cid or not isinstance(data.get("deleted_ts"), str):
|
|
377
|
+
fail(
|
|
378
|
+
"slack_evidence",
|
|
379
|
+
"A deletion needs existing state, channel and exact deleted_ts",
|
|
380
|
+
"/evidence/deletion",
|
|
381
|
+
)
|
|
382
|
+
if not isinstance(source, dict) or set(source) != {"reference", "digest"}:
|
|
383
|
+
fail("slack_evidence", "Deletion evidence source is required", "/evidence/deletion/source")
|
|
384
|
+
deletions.append((cid, data["deleted_ts"], source))
|
|
385
|
+
else:
|
|
386
|
+
fail("slack_evidence", "Unsupported captured method", "/evidence/observations/method")
|
|
387
|
+
|
|
388
|
+
mutations = []
|
|
389
|
+
for token, removal in sorted(removals.items()):
|
|
390
|
+
if token in records:
|
|
391
|
+
fail(
|
|
392
|
+
"slack_evidence",
|
|
393
|
+
"Conflicting complete collection observations",
|
|
394
|
+
"/evidence/observations",
|
|
395
|
+
entity=token[0],
|
|
396
|
+
)
|
|
397
|
+
mutations.append(removal)
|
|
398
|
+
for (entity, _), record in sorted(records.items()):
|
|
399
|
+
prior = existing.get(entity, record["key"]) if existing is not None else None
|
|
400
|
+
value = _merge(prior, record["value"]) if prior else record["value"]
|
|
401
|
+
source = {
|
|
402
|
+
"record": "slack:" + entity + ":" + hashlib.sha256(encoded(record["key"])).hexdigest(),
|
|
403
|
+
"evidence": sorted(record["evidence"], key=lambda e: (e["reference"], e["digest"])),
|
|
404
|
+
}
|
|
405
|
+
mutation = {"op": "update" if prior else "insert", "entity": entity, "value": value, "source": source}
|
|
406
|
+
if prior:
|
|
407
|
+
mutation["key"] = record["key"]
|
|
408
|
+
mutations.append(mutation)
|
|
409
|
+
deleted_keys = set()
|
|
410
|
+
for cid, ts, evidence_source in sorted(deletions, key=lambda row: (row[0], row[1])):
|
|
411
|
+
for entity in (
|
|
412
|
+
"slack_reaction_users",
|
|
413
|
+
"slack_reaction_summaries",
|
|
414
|
+
"slack_message_user_states",
|
|
415
|
+
"slack_file_shares",
|
|
416
|
+
"slack_message_files",
|
|
417
|
+
"slack_unavailable_message_files",
|
|
418
|
+
"slack_pins",
|
|
419
|
+
"slack_message_threads",
|
|
420
|
+
"slack_messages",
|
|
421
|
+
):
|
|
422
|
+
field = (
|
|
423
|
+
"ts"
|
|
424
|
+
if entity == "slack_messages"
|
|
425
|
+
else "reply_ts" if entity == "slack_message_threads" else "message_ts"
|
|
426
|
+
)
|
|
427
|
+
for row in existing.list(entity):
|
|
428
|
+
if row.get("workspace_id") != wid or row.get("channel_id") != cid or row.get(field) != ts:
|
|
429
|
+
continue
|
|
430
|
+
key = _identity(contract, entity, row)
|
|
431
|
+
token = entity, encoded(key)
|
|
432
|
+
if token in deleted_keys:
|
|
433
|
+
continue
|
|
434
|
+
if token in records:
|
|
435
|
+
fail(
|
|
436
|
+
"slack_evidence",
|
|
437
|
+
"Capture both updates and deletes the same record",
|
|
438
|
+
"/evidence/deletion",
|
|
439
|
+
entity=entity,
|
|
440
|
+
)
|
|
441
|
+
deleted_keys.add(token)
|
|
442
|
+
mutations.append(
|
|
443
|
+
{
|
|
444
|
+
"op": "delete",
|
|
445
|
+
"entity": entity,
|
|
446
|
+
"key": key,
|
|
447
|
+
"source": {
|
|
448
|
+
"record": "slack:message.deleted:" + hashlib.sha256(encoded(key)).hexdigest(),
|
|
449
|
+
"evidence": [evidence_source],
|
|
450
|
+
},
|
|
451
|
+
}
|
|
452
|
+
)
|
|
453
|
+
return {
|
|
454
|
+
"id": batch_id or "slack." + hashlib.sha256(encoded(evidence)).hexdigest(),
|
|
455
|
+
"target": contract.target,
|
|
456
|
+
"mapping": MAPPING,
|
|
457
|
+
"coverage": "partial",
|
|
458
|
+
"mutations": mutations,
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
|
|
462
|
+
def synthetic_slack_evidence() -> dict:
|
|
463
|
+
"""An invented workspace with members, threads, rich content and file variants."""
|
|
464
|
+
observations = []
|
|
465
|
+
|
|
466
|
+
def add(method, data, **context):
|
|
467
|
+
digest = hashlib.sha256(encoded({"method": method, "context": context, "data": data})).hexdigest()
|
|
468
|
+
observations.append(
|
|
469
|
+
{
|
|
470
|
+
"method": method,
|
|
471
|
+
"context": context,
|
|
472
|
+
"data": {"ok": True, **data},
|
|
473
|
+
"source": {"reference": "synthetic:slack:" + digest, "digest": digest},
|
|
474
|
+
}
|
|
475
|
+
)
|
|
476
|
+
|
|
477
|
+
add("auth.test", {"team_id": "TSYNTH", "user_id": "UAGENT"})
|
|
478
|
+
add(
|
|
479
|
+
"users.list",
|
|
480
|
+
{
|
|
481
|
+
"members": [
|
|
482
|
+
{
|
|
483
|
+
"id": "UAGENT",
|
|
484
|
+
"name": "gateway-helper",
|
|
485
|
+
"real_name": "Gateway Helper",
|
|
486
|
+
"is_bot": False,
|
|
487
|
+
"deleted": False,
|
|
488
|
+
"profile": {"display_name": "Helper", "email": None},
|
|
489
|
+
},
|
|
490
|
+
{
|
|
491
|
+
"id": "UALICE",
|
|
492
|
+
"name": "alice",
|
|
493
|
+
"real_name": "Alice Rivers",
|
|
494
|
+
"deleted": False,
|
|
495
|
+
"profile": {
|
|
496
|
+
"display_name": "Alice",
|
|
497
|
+
"title": "Release owner",
|
|
498
|
+
"fields": {"XROLE": {"value": "Engineering", "alt": ""}},
|
|
499
|
+
},
|
|
500
|
+
},
|
|
501
|
+
{"id": "UBOB", "name": "bob", "real_name": "Bob Chen", "deleted": False},
|
|
502
|
+
{"id": "UFORMER", "name": "former", "deleted": True},
|
|
503
|
+
{"id": "UBOT", "name": "build-bot", "is_bot": True, "deleted": False},
|
|
504
|
+
]
|
|
505
|
+
},
|
|
506
|
+
)
|
|
507
|
+
channels = [
|
|
508
|
+
{
|
|
509
|
+
"id": "CGENERAL",
|
|
510
|
+
"name": "release-handoff",
|
|
511
|
+
"is_channel": True,
|
|
512
|
+
"is_private": False,
|
|
513
|
+
"is_archived": False,
|
|
514
|
+
"num_members": 3,
|
|
515
|
+
"is_member": True,
|
|
516
|
+
"topic": {"value": "Release readiness and ownership", "creator": "UALICE", "last_set": 1700000000},
|
|
517
|
+
},
|
|
518
|
+
{
|
|
519
|
+
"id": "CPUBLIC",
|
|
520
|
+
"name": "engineering",
|
|
521
|
+
"is_channel": True,
|
|
522
|
+
"is_private": False,
|
|
523
|
+
"is_archived": False,
|
|
524
|
+
"is_member": False,
|
|
525
|
+
},
|
|
526
|
+
{
|
|
527
|
+
"id": "CPRIVATE",
|
|
528
|
+
"name": "incident-room",
|
|
529
|
+
"is_channel": True,
|
|
530
|
+
"is_private": True,
|
|
531
|
+
"is_archived": False,
|
|
532
|
+
"is_member": True,
|
|
533
|
+
},
|
|
534
|
+
{
|
|
535
|
+
"id": "CSECRET",
|
|
536
|
+
"name": "restricted",
|
|
537
|
+
"is_channel": True,
|
|
538
|
+
"is_private": True,
|
|
539
|
+
"is_archived": False,
|
|
540
|
+
"is_member": False,
|
|
541
|
+
},
|
|
542
|
+
{
|
|
543
|
+
"id": "CARCHIVED",
|
|
544
|
+
"name": "previous-release",
|
|
545
|
+
"is_channel": True,
|
|
546
|
+
"is_private": False,
|
|
547
|
+
"is_archived": True,
|
|
548
|
+
"is_member": True,
|
|
549
|
+
},
|
|
550
|
+
{"id": "DPAIR", "is_im": True, "is_private": True, "is_member": True, "user": "UALICE"},
|
|
551
|
+
{"id": "GTEAM", "is_mpim": True, "is_private": True, "is_member": True},
|
|
552
|
+
]
|
|
553
|
+
add("conversations.list", {"channels": channels})
|
|
554
|
+
for cid in ("CGENERAL", "CPRIVATE", "CARCHIVED", "DPAIR", "GTEAM"):
|
|
555
|
+
add(
|
|
556
|
+
"conversations.members",
|
|
557
|
+
{"members": ["UAGENT", "UALICE", "UBOB"] if cid == "CGENERAL" else ["UAGENT", "UALICE"]},
|
|
558
|
+
channel_id=cid,
|
|
559
|
+
)
|
|
560
|
+
add("conversations.members", {"members": ["UALICE", "UBOB"]}, channel_id="CSECRET")
|
|
561
|
+
parent = "1700000000.000000000000000001"
|
|
562
|
+
add(
|
|
563
|
+
"conversations.history",
|
|
564
|
+
{
|
|
565
|
+
"messages": [
|
|
566
|
+
{
|
|
567
|
+
"ts": parent,
|
|
568
|
+
"type": "message",
|
|
569
|
+
"user": "UALICE",
|
|
570
|
+
"text": "Please review the release checklist, acknowledge it, and reply here with the handoff owner and next update.",
|
|
571
|
+
"reply_count": 1,
|
|
572
|
+
"blocks": [
|
|
573
|
+
{
|
|
574
|
+
"type": "section",
|
|
575
|
+
"text": {
|
|
576
|
+
"type": "mrkdwn",
|
|
577
|
+
"text": "*Release handoff* — owner Alice; next update 17:00 UTC.",
|
|
578
|
+
},
|
|
579
|
+
}
|
|
580
|
+
],
|
|
581
|
+
"reactions": [{"name": "eyes", "count": 1, "users": ["UBOB"]}],
|
|
582
|
+
"files": [
|
|
583
|
+
{
|
|
584
|
+
"id": "FCHECKLIST",
|
|
585
|
+
"name": "release-checklist.md",
|
|
586
|
+
"title": "Release checklist",
|
|
587
|
+
"mode": "hosted",
|
|
588
|
+
"mimetype": "text/markdown",
|
|
589
|
+
"size": 512,
|
|
590
|
+
"user": "UALICE",
|
|
591
|
+
"is_public": False,
|
|
592
|
+
}
|
|
593
|
+
],
|
|
594
|
+
},
|
|
595
|
+
{
|
|
596
|
+
"ts": "1700000002.000001",
|
|
597
|
+
"type": "message",
|
|
598
|
+
"user": "UBOT",
|
|
599
|
+
"bot_id": "BPIPELINE",
|
|
600
|
+
"text": "Build passed; deployment awaits review.",
|
|
601
|
+
"files": [{"id": "FRESTRICTED", "file_access": "check_file_info"}],
|
|
602
|
+
},
|
|
603
|
+
]
|
|
604
|
+
},
|
|
605
|
+
channel_id="CGENERAL",
|
|
606
|
+
)
|
|
607
|
+
add(
|
|
608
|
+
"conversations.replies",
|
|
609
|
+
{
|
|
610
|
+
"messages": [
|
|
611
|
+
{"ts": parent, "type": "message", "user": "UALICE", "thread_ts": parent, "reply_count": 1},
|
|
612
|
+
{
|
|
613
|
+
"ts": "1700000000.000000000000000002",
|
|
614
|
+
"thread_ts": parent,
|
|
615
|
+
"type": "message",
|
|
616
|
+
"user": "UBOB",
|
|
617
|
+
"text": "Tests complete. Please confirm the final owner and next update.",
|
|
618
|
+
},
|
|
619
|
+
]
|
|
620
|
+
},
|
|
621
|
+
channel_id="CGENERAL",
|
|
622
|
+
thread_ts=parent,
|
|
623
|
+
)
|
|
624
|
+
add(
|
|
625
|
+
"bookmarks.list",
|
|
626
|
+
{
|
|
627
|
+
"bookmarks": [
|
|
628
|
+
{
|
|
629
|
+
"id": "BMRELEASE",
|
|
630
|
+
"type": "link",
|
|
631
|
+
"title": "Release checklist",
|
|
632
|
+
"rank": "0",
|
|
633
|
+
"link": "https://example.invalid/release-checklist",
|
|
634
|
+
}
|
|
635
|
+
]
|
|
636
|
+
},
|
|
637
|
+
channel_id="CGENERAL",
|
|
638
|
+
)
|
|
639
|
+
add("users.getPresence", {"presence": "active"}, user_id="UALICE")
|
|
640
|
+
add(
|
|
641
|
+
"usergroups.list",
|
|
642
|
+
{
|
|
643
|
+
"usergroups": [
|
|
644
|
+
{
|
|
645
|
+
"id": "SRELEASE",
|
|
646
|
+
"name": "Release team",
|
|
647
|
+
"handle": "release",
|
|
648
|
+
"users": ["UALICE", "UBOB"],
|
|
649
|
+
"user_count": 2,
|
|
650
|
+
}
|
|
651
|
+
]
|
|
652
|
+
},
|
|
653
|
+
)
|
|
654
|
+
return {
|
|
655
|
+
"format": FORMAT,
|
|
656
|
+
"synthetic": True,
|
|
657
|
+
"workspace": {"id": "TSYNTH", "name": "Synthetic Release Team", "domain": "synthetic-release"},
|
|
658
|
+
"principal": {"user_id": "UAGENT"},
|
|
659
|
+
"observations": observations,
|
|
660
|
+
"coverage": {"complete": False, "reasons": ["Invented fixture; no live workspace evidence."]},
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
|
|
664
|
+
def build_slack_bundle(destination: str | Path, *, evidence: dict | None = None, name: str | None = None) -> Contract:
|
|
665
|
+
"""Create a populated executable Slack bundle in a new/empty directory."""
|
|
666
|
+
evidence = synthetic_slack_evidence() if evidence is None else evidence
|
|
667
|
+
workspace, principal = _envelope(evidence)
|
|
668
|
+
destination = Path(destination)
|
|
669
|
+
contract = init_bundle(destination, connector="slack", name=name or "slack-world")
|
|
670
|
+
try:
|
|
671
|
+
diagnostics = {}
|
|
672
|
+
batch = map_slack_evidence(evidence, contract, diagnostics=diagnostics)
|
|
673
|
+
with tempfile.TemporaryDirectory(prefix="gateway-slack-") as temporary:
|
|
674
|
+
with SQLiteStore.create(Path(temporary) / "state.sqlite", contract) as store:
|
|
675
|
+
store.apply_batch(batch)
|
|
676
|
+
if store.get("slack_users", {"workspace_id": workspace["id"], "id": principal["user_id"]}) is None:
|
|
677
|
+
fail("slack_evidence", "The authenticated principal was not captured", "/evidence/principal")
|
|
678
|
+
store.audit()
|
|
679
|
+
(destination / "data/initial.json").write_bytes(encoded(batch))
|
|
680
|
+
(destination / "tools/slack-principal.json").write_bytes(
|
|
681
|
+
encoded({"workspace_id": workspace["id"], "user_id": principal["user_id"]})
|
|
682
|
+
)
|
|
683
|
+
(destination / "fidelity.json").write_bytes(
|
|
684
|
+
encoded(fidelity_report(contract, evidence, batch, diagnostics, mapping=MAPPING))
|
|
685
|
+
)
|
|
686
|
+
(destination / "capabilities.json").write_bytes(encoded(load_capabilities()))
|
|
687
|
+
write_scenario(destination, evidence, batch, entities=contract.entities)
|
|
688
|
+
(destination / "slack-imports.json").write_bytes(
|
|
689
|
+
encoded({batch["id"]: hashlib.sha256(encoded(evidence)).hexdigest()})
|
|
690
|
+
)
|
|
691
|
+
check_bundle(destination)
|
|
692
|
+
return contract
|
|
693
|
+
except BaseException:
|
|
694
|
+
shutil.rmtree(destination)
|
|
695
|
+
raise
|
|
696
|
+
|
|
697
|
+
|
|
698
|
+
def refresh_slack_bundle(destination: str | Path, evidence: dict | None = None, *, batch_id: str | None = None) -> dict:
|
|
699
|
+
"""Validate then append one refresh batch; preserve original batch and lineage.
|
|
700
|
+
|
|
701
|
+
Call this only on an isolated copy while constructing a new immutable version.
|
|
702
|
+
The publisher owns expected-parent CAS and immutable version promotion.
|
|
703
|
+
"""
|
|
704
|
+
destination = Path(destination)
|
|
705
|
+
evidence = synthetic_slack_evidence() if evidence is None else evidence
|
|
706
|
+
workspace, principal = _envelope(evidence)
|
|
707
|
+
pinned = read_json(destination / "tools/slack-principal.json")
|
|
708
|
+
if pinned != {"workspace_id": workspace["id"], "user_id": principal["user_id"]}:
|
|
709
|
+
fail("slack_evidence", "Refresh cannot change the bound workspace or principal", "/evidence/principal")
|
|
710
|
+
contract = check_bundle(destination)
|
|
711
|
+
previous_report = read_json(destination / "fidelity.json")
|
|
712
|
+
if previous_report.get("template_digest") != slack_template_digest():
|
|
713
|
+
fail(
|
|
714
|
+
"slack_template_mismatch",
|
|
715
|
+
"Refresh requires the original template package; create a new world with the current template",
|
|
716
|
+
"/fidelity/template_digest",
|
|
717
|
+
)
|
|
718
|
+
digest = hashlib.sha256(encoded(evidence)).hexdigest()
|
|
719
|
+
batch_id = batch_id or "slack." + digest
|
|
720
|
+
imports = read_json(destination / "slack-imports.json")
|
|
721
|
+
if batch_id in imports:
|
|
722
|
+
if imports[batch_id] != digest:
|
|
723
|
+
fail("idempotency_conflict", "Refresh ID was used for different evidence", "/evidence")
|
|
724
|
+
return {"status": "already_applied", "batch": batch_id, "applied": 0}
|
|
725
|
+
batches = sorted((destination / "data/batches").glob("*.json"))
|
|
726
|
+
with tempfile.TemporaryDirectory(prefix="gateway-slack-refresh-") as temporary:
|
|
727
|
+
with SQLiteStore.create(Path(temporary) / "state.sqlite", contract) as store:
|
|
728
|
+
store.apply_batch(read_json(destination / "data/initial.json"))
|
|
729
|
+
for path in batches:
|
|
730
|
+
store.apply_batch(read_json(path))
|
|
731
|
+
diagnostics = {}
|
|
732
|
+
batch = map_slack_evidence(evidence, contract, existing=store, batch_id=batch_id, diagnostics=diagnostics)
|
|
733
|
+
result = store.apply_batch(batch)
|
|
734
|
+
store.audit()
|
|
735
|
+
state = {
|
|
736
|
+
"mutations": [
|
|
737
|
+
{"op": "insert", "entity": entity, "value": value}
|
|
738
|
+
for entity in sorted(contract.entities)
|
|
739
|
+
for value in store.list(entity)
|
|
740
|
+
]
|
|
741
|
+
}
|
|
742
|
+
target = (
|
|
743
|
+
destination
|
|
744
|
+
/ "data/batches"
|
|
745
|
+
/ f"{len(batches) + 1:06d}-{hashlib.sha256(batch_id.encode()).hexdigest()[:16]}.json"
|
|
746
|
+
)
|
|
747
|
+
target.parent.mkdir(exist_ok=True)
|
|
748
|
+
temporary = target.with_suffix(".pending")
|
|
749
|
+
temporary.write_bytes(encoded(batch))
|
|
750
|
+
temporary.replace(target)
|
|
751
|
+
imports[batch_id] = digest
|
|
752
|
+
(destination / "slack-imports.json").write_bytes(encoded(imports))
|
|
753
|
+
(destination / "fidelity.json").write_bytes(
|
|
754
|
+
encoded(fidelity_report(contract, evidence, batch, diagnostics, mapping=MAPPING))
|
|
755
|
+
)
|
|
756
|
+
write_scenario(destination, evidence, state, entities=contract.entities)
|
|
757
|
+
return result
|