gatewaysdk 0.3.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (376) hide show
  1. gatewaysdk/__init__.py +540 -0
  2. gatewaysdk/adapter/__init__.py +15 -0
  3. gatewaysdk/adapter/base.py +94 -0
  4. gatewaysdk/adapter/messages.py +270 -0
  5. gatewaysdk/adapter/triplet.py +1026 -0
  6. gatewaysdk/algorithm/__init__.py +39 -0
  7. gatewaysdk/algorithm/apo/__init__.py +5 -0
  8. gatewaysdk/algorithm/apo/apo.py +898 -0
  9. gatewaysdk/algorithm/apo/prompts/apply_edit_variant01.poml +22 -0
  10. gatewaysdk/algorithm/apo/prompts/apply_edit_variant02.poml +18 -0
  11. gatewaysdk/algorithm/apo/prompts/text_gradient_variant01.poml +18 -0
  12. gatewaysdk/algorithm/apo/prompts/text_gradient_variant02.poml +16 -0
  13. gatewaysdk/algorithm/apo/prompts/text_gradient_variant03.poml +107 -0
  14. gatewaysdk/algorithm/base.py +262 -0
  15. gatewaysdk/algorithm/decorator.py +264 -0
  16. gatewaysdk/algorithm/evals/__init__.py +7 -0
  17. gatewaysdk/algorithm/evals/evals.py +217 -0
  18. gatewaysdk/algorithm/fast.py +250 -0
  19. gatewaysdk/algorithm/gepa/__init__.py +61 -0
  20. gatewaysdk/algorithm/gepa/adapter.py +495 -0
  21. gatewaysdk/algorithm/gepa/gepa.py +570 -0
  22. gatewaysdk/algorithm/gepa/lib/__init__.py +18 -0
  23. gatewaysdk/algorithm/gepa/lib/adapters/README.md +12 -0
  24. gatewaysdk/algorithm/gepa/lib/adapters/__init__.py +0 -0
  25. gatewaysdk/algorithm/gepa/lib/adapters/anymaths_adapter/README.md +341 -0
  26. gatewaysdk/algorithm/gepa/lib/adapters/anymaths_adapter/__init__.py +1 -0
  27. gatewaysdk/algorithm/gepa/lib/adapters/anymaths_adapter/anymaths_adapter.py +174 -0
  28. gatewaysdk/algorithm/gepa/lib/adapters/anymaths_adapter/requirements.txt +1 -0
  29. gatewaysdk/algorithm/gepa/lib/adapters/default_adapter/README.md +0 -0
  30. gatewaysdk/algorithm/gepa/lib/adapters/default_adapter/__init__.py +0 -0
  31. gatewaysdk/algorithm/gepa/lib/adapters/default_adapter/default_adapter.py +209 -0
  32. gatewaysdk/algorithm/gepa/lib/adapters/dspy_adapter/README.md +7 -0
  33. gatewaysdk/algorithm/gepa/lib/adapters/dspy_adapter/__init__.py +0 -0
  34. gatewaysdk/algorithm/gepa/lib/adapters/dspy_adapter/dspy_adapter.py +307 -0
  35. gatewaysdk/algorithm/gepa/lib/adapters/dspy_full_program_adapter/README.md +99 -0
  36. gatewaysdk/algorithm/gepa/lib/adapters/dspy_full_program_adapter/dspy_program_proposal_signature.py +137 -0
  37. gatewaysdk/algorithm/gepa/lib/adapters/dspy_full_program_adapter/full_program_adapter.py +268 -0
  38. gatewaysdk/algorithm/gepa/lib/adapters/generic_rag_adapter/GEPA_RAG.md +621 -0
  39. gatewaysdk/algorithm/gepa/lib/adapters/generic_rag_adapter/__init__.py +56 -0
  40. gatewaysdk/algorithm/gepa/lib/adapters/generic_rag_adapter/evaluation_metrics.py +226 -0
  41. gatewaysdk/algorithm/gepa/lib/adapters/generic_rag_adapter/generic_rag_adapter.py +496 -0
  42. gatewaysdk/algorithm/gepa/lib/adapters/generic_rag_adapter/rag_pipeline.py +238 -0
  43. gatewaysdk/algorithm/gepa/lib/adapters/generic_rag_adapter/vector_store_interface.py +212 -0
  44. gatewaysdk/algorithm/gepa/lib/adapters/generic_rag_adapter/vector_stores/__init__.py +2 -0
  45. gatewaysdk/algorithm/gepa/lib/adapters/generic_rag_adapter/vector_stores/chroma_store.py +196 -0
  46. gatewaysdk/algorithm/gepa/lib/adapters/generic_rag_adapter/vector_stores/lancedb_store.py +422 -0
  47. gatewaysdk/algorithm/gepa/lib/adapters/generic_rag_adapter/vector_stores/milvus_store.py +409 -0
  48. gatewaysdk/algorithm/gepa/lib/adapters/generic_rag_adapter/vector_stores/qdrant_store.py +368 -0
  49. gatewaysdk/algorithm/gepa/lib/adapters/generic_rag_adapter/vector_stores/weaviate_store.py +418 -0
  50. gatewaysdk/algorithm/gepa/lib/adapters/mcp_adapter/README.md +552 -0
  51. gatewaysdk/algorithm/gepa/lib/adapters/mcp_adapter/__init__.py +37 -0
  52. gatewaysdk/algorithm/gepa/lib/adapters/mcp_adapter/mcp_adapter.py +699 -0
  53. gatewaysdk/algorithm/gepa/lib/adapters/mcp_adapter/mcp_client.py +364 -0
  54. gatewaysdk/algorithm/gepa/lib/adapters/terminal_bench_adapter/README.md +9 -0
  55. gatewaysdk/algorithm/gepa/lib/adapters/terminal_bench_adapter/__init__.py +0 -0
  56. gatewaysdk/algorithm/gepa/lib/adapters/terminal_bench_adapter/terminal_bench_adapter.py +217 -0
  57. gatewaysdk/algorithm/gepa/lib/api.py +382 -0
  58. gatewaysdk/algorithm/gepa/lib/core/__init__.py +0 -0
  59. gatewaysdk/algorithm/gepa/lib/core/adapter.py +180 -0
  60. gatewaysdk/algorithm/gepa/lib/core/data_loader.py +74 -0
  61. gatewaysdk/algorithm/gepa/lib/core/engine.py +379 -0
  62. gatewaysdk/algorithm/gepa/lib/core/result.py +233 -0
  63. gatewaysdk/algorithm/gepa/lib/core/state.py +636 -0
  64. gatewaysdk/algorithm/gepa/lib/examples/__init__.py +0 -0
  65. gatewaysdk/algorithm/gepa/lib/examples/aime.py +24 -0
  66. gatewaysdk/algorithm/gepa/lib/examples/anymaths-bench/eval_default.py +111 -0
  67. gatewaysdk/algorithm/gepa/lib/examples/anymaths-bench/prompt-templates/instruction_prompt.txt +9 -0
  68. gatewaysdk/algorithm/gepa/lib/examples/anymaths-bench/prompt-templates/optimal_prompt.txt +24 -0
  69. gatewaysdk/algorithm/gepa/lib/examples/anymaths-bench/train_anymaths.py +177 -0
  70. gatewaysdk/algorithm/gepa/lib/examples/dspy_full_program_evolution/arc_agi.ipynb +25705 -0
  71. gatewaysdk/algorithm/gepa/lib/examples/dspy_full_program_evolution/example.ipynb +348 -0
  72. gatewaysdk/algorithm/gepa/lib/examples/mcp_adapter/__init__.py +4 -0
  73. gatewaysdk/algorithm/gepa/lib/examples/mcp_adapter/mcp_optimization_example.py +456 -0
  74. gatewaysdk/algorithm/gepa/lib/examples/rag_adapter/RAG_GUIDE.md +613 -0
  75. gatewaysdk/algorithm/gepa/lib/examples/rag_adapter/__init__.py +9 -0
  76. gatewaysdk/algorithm/gepa/lib/examples/rag_adapter/rag_optimization.py +820 -0
  77. gatewaysdk/algorithm/gepa/lib/examples/rag_adapter/requirements-rag.txt +29 -0
  78. gatewaysdk/algorithm/gepa/lib/examples/terminal-bench/prompt-templates/instruction_prompt.txt +16 -0
  79. gatewaysdk/algorithm/gepa/lib/examples/terminal-bench/prompt-templates/terminus.txt +9 -0
  80. gatewaysdk/algorithm/gepa/lib/examples/terminal-bench/train_terminus.py +161 -0
  81. gatewaysdk/algorithm/gepa/lib/gepa_utils.py +117 -0
  82. gatewaysdk/algorithm/gepa/lib/logging/__init__.py +0 -0
  83. gatewaysdk/algorithm/gepa/lib/logging/experiment_tracker.py +187 -0
  84. gatewaysdk/algorithm/gepa/lib/logging/logger.py +75 -0
  85. gatewaysdk/algorithm/gepa/lib/logging/utils.py +103 -0
  86. gatewaysdk/algorithm/gepa/lib/proposer/__init__.py +0 -0
  87. gatewaysdk/algorithm/gepa/lib/proposer/base.py +31 -0
  88. gatewaysdk/algorithm/gepa/lib/proposer/merge.py +357 -0
  89. gatewaysdk/algorithm/gepa/lib/proposer/reflective_mutation/__init__.py +0 -0
  90. gatewaysdk/algorithm/gepa/lib/proposer/reflective_mutation/base.py +49 -0
  91. gatewaysdk/algorithm/gepa/lib/proposer/reflective_mutation/reflective_mutation.py +176 -0
  92. gatewaysdk/algorithm/gepa/lib/py.typed +0 -0
  93. gatewaysdk/algorithm/gepa/lib/strategies/__init__.py +0 -0
  94. gatewaysdk/algorithm/gepa/lib/strategies/batch_sampler.py +77 -0
  95. gatewaysdk/algorithm/gepa/lib/strategies/candidate_selector.py +50 -0
  96. gatewaysdk/algorithm/gepa/lib/strategies/component_selector.py +36 -0
  97. gatewaysdk/algorithm/gepa/lib/strategies/eval_policy.py +64 -0
  98. gatewaysdk/algorithm/gepa/lib/strategies/instruction_proposal.py +126 -0
  99. gatewaysdk/algorithm/gepa/lib/utils/__init__.py +10 -0
  100. gatewaysdk/algorithm/gepa/lib/utils/stop_condition.py +196 -0
  101. gatewaysdk/algorithm/gepa/tracing.py +105 -0
  102. gatewaysdk/algorithm/utils.py +177 -0
  103. gatewaysdk/algorithm/verl/__init__.py +5 -0
  104. gatewaysdk/algorithm/verl/interface.py +202 -0
  105. gatewaysdk/automations.py +111 -0
  106. gatewaysdk/benchmark_hub/README.md +88 -0
  107. gatewaysdk/benchmark_hub/__init__.py +119 -0
  108. gatewaysdk/benchmark_hub/_archive.py +189 -0
  109. gatewaysdk/benchmark_hub/_tar.py +6 -0
  110. gatewaysdk/benchmark_hub/client.py +992 -0
  111. gatewaysdk/benchmark_hub/dispatch_shard.py +34 -0
  112. gatewaysdk/benchmark_hub/eval_config.py +20 -0
  113. gatewaysdk/benchmark_hub/evals.py +351 -0
  114. gatewaysdk/benchmark_hub/harbor_adapter.py +673 -0
  115. gatewaysdk/benchmark_hub/path_utils.py +81 -0
  116. gatewaysdk/benchmark_hub/save_utils.py +101 -0
  117. gatewaysdk/benchmark_hub/verifiers_adapter.py +327 -0
  118. gatewaysdk/benchmark_hub/versioning.py +72 -0
  119. gatewaysdk/build.py +515 -0
  120. gatewaysdk/cli/__init__.py +58 -0
  121. gatewaysdk/cli/agent_runner.py +132 -0
  122. gatewaysdk/cli/http_client.py +115 -0
  123. gatewaysdk/cli/platform.py +4086 -0
  124. gatewaysdk/cli/prometheus.py +115 -0
  125. gatewaysdk/cli/release_gate.py +215 -0
  126. gatewaysdk/cli/store.py +131 -0
  127. gatewaysdk/cli/vllm.py +29 -0
  128. gatewaysdk/client.py +406 -0
  129. gatewaysdk/config.py +348 -0
  130. gatewaysdk/connectors/__init__.py +25 -0
  131. gatewaysdk/connectors/client.py +203 -0
  132. gatewaysdk/connectors/skill.py +25 -0
  133. gatewaysdk/connectors/template.py +106 -0
  134. gatewaysdk/context.py +606 -0
  135. gatewaysdk/emitter/__init__.py +43 -0
  136. gatewaysdk/emitter/annotation.py +370 -0
  137. gatewaysdk/emitter/exception.py +54 -0
  138. gatewaysdk/emitter/message.py +61 -0
  139. gatewaysdk/emitter/object.py +117 -0
  140. gatewaysdk/emitter/reward.py +320 -0
  141. gatewaysdk/env_var.py +156 -0
  142. gatewaysdk/environment/__init__.py +108 -0
  143. gatewaysdk/environment/_bundle.py +281 -0
  144. gatewaysdk/environment/_harbor.py +276 -0
  145. gatewaysdk/environment/_materialize.py +97 -0
  146. gatewaysdk/environment/_tar.py +33 -0
  147. gatewaysdk/environment/_world.py +346 -0
  148. gatewaysdk/environment/core.py +527 -0
  149. gatewaysdk/environment/errors.py +58 -0
  150. gatewaysdk/environment/runtime.py +150 -0
  151. gatewaysdk/environment/schema/__init__.py +35 -0
  152. gatewaysdk/environment/schema/__main__.py +225 -0
  153. gatewaysdk/environment/schema/_slack_fidelity.py +116 -0
  154. gatewaysdk/environment/schema/_slack_scenario.py +183 -0
  155. gatewaysdk/environment/schema/api.py +1628 -0
  156. gatewaysdk/environment/schema/batch.py +755 -0
  157. gatewaysdk/environment/schema/compiler.py +615 -0
  158. gatewaysdk/environment/schema/conform.py +428 -0
  159. gatewaysdk/environment/schema/connector.py +777 -0
  160. gatewaysdk/environment/schema/connectors/apple-business-manager/handlers.py +17 -0
  161. gatewaysdk/environment/schema/connectors/apple-business-manager/parity.json +28 -0
  162. gatewaysdk/environment/schema/connectors/apple-business-manager/provenance.json +233 -0
  163. gatewaysdk/environment/schema/connectors/apple-business-manager/scope.toml +112 -0
  164. gatewaysdk/environment/schema/connectors/apple-business-manager/world.json +1380 -0
  165. gatewaysdk/environment/schema/connectors/base.json +28 -0
  166. gatewaysdk/environment/schema/connectors/custom/handlers.py +18 -0
  167. gatewaysdk/environment/schema/connectors/custom/world.json +123 -0
  168. gatewaysdk/environment/schema/connectors/github/handlers.py +81 -0
  169. gatewaysdk/environment/schema/connectors/github/parity.json +37 -0
  170. gatewaysdk/environment/schema/connectors/github/provenance.json +134 -0
  171. gatewaysdk/environment/schema/connectors/github/scope.toml +158 -0
  172. gatewaysdk/environment/schema/connectors/github/world.json +5869 -0
  173. gatewaysdk/environment/schema/connectors/google-calendar/handlers.py +23 -0
  174. gatewaysdk/environment/schema/connectors/google-calendar/world.json +209 -0
  175. gatewaysdk/environment/schema/connectors/google-drive/handlers.py +18 -0
  176. gatewaysdk/environment/schema/connectors/google-drive/world.json +286 -0
  177. gatewaysdk/environment/schema/connectors/jamf/handlers.py +34 -0
  178. gatewaysdk/environment/schema/connectors/jamf/parity.json +49 -0
  179. gatewaysdk/environment/schema/connectors/jamf/provenance.json +181 -0
  180. gatewaysdk/environment/schema/connectors/jamf/scope.toml +128 -0
  181. gatewaysdk/environment/schema/connectors/jamf/world.json +2218 -0
  182. gatewaysdk/environment/schema/connectors/jira/handlers.py +28 -0
  183. gatewaysdk/environment/schema/connectors/jira/world.json +389 -0
  184. gatewaysdk/environment/schema/connectors/kandji/handlers.py +38 -0
  185. gatewaysdk/environment/schema/connectors/kandji/parity.json +29 -0
  186. gatewaysdk/environment/schema/connectors/kandji/provenance.json +152 -0
  187. gatewaysdk/environment/schema/connectors/kandji/scope.toml +120 -0
  188. gatewaysdk/environment/schema/connectors/kandji/world.json +765 -0
  189. gatewaysdk/environment/schema/connectors/linear/handlers.py +20 -0
  190. gatewaysdk/environment/schema/connectors/linear/world.json +363 -0
  191. gatewaysdk/environment/schema/connectors/microsoft-teams/fidelity.json +45 -0
  192. gatewaysdk/environment/schema/connectors/microsoft-teams/handlers.py +1 -0
  193. gatewaysdk/environment/schema/connectors/microsoft-teams/provenance.json +851 -0
  194. gatewaysdk/environment/schema/connectors/microsoft-teams/scope.md +231 -0
  195. gatewaysdk/environment/schema/connectors/microsoft-teams/teams-types.json +1710 -0
  196. gatewaysdk/environment/schema/connectors/microsoft-teams/world.json +858 -0
  197. gatewaysdk/environment/schema/connectors/netsuite/handlers.py +32 -0
  198. gatewaysdk/environment/schema/connectors/netsuite/parity.json +50 -0
  199. gatewaysdk/environment/schema/connectors/netsuite/provenance.json +137 -0
  200. gatewaysdk/environment/schema/connectors/netsuite/scope.toml +78 -0
  201. gatewaysdk/environment/schema/connectors/netsuite/world.json +16243 -0
  202. gatewaysdk/environment/schema/connectors/salesforce/handlers.py +806 -0
  203. gatewaysdk/environment/schema/connectors/salesforce/parity.json +46 -0
  204. gatewaysdk/environment/schema/connectors/salesforce/provenance.json +143 -0
  205. gatewaysdk/environment/schema/connectors/salesforce/scope.toml +174 -0
  206. gatewaysdk/environment/schema/connectors/salesforce/world.json +4695 -0
  207. gatewaysdk/environment/schema/connectors/slack/MODEL.md +236 -0
  208. gatewaysdk/environment/schema/connectors/slack/capabilities.json +1039 -0
  209. gatewaysdk/environment/schema/connectors/slack/handlers.py +762 -0
  210. gatewaysdk/environment/schema/connectors/slack/parity.json +65 -0
  211. gatewaysdk/environment/schema/connectors/slack/provenance.json +294 -0
  212. gatewaysdk/environment/schema/connectors/slack/scope.toml +44 -0
  213. gatewaysdk/environment/schema/connectors/slack/world.json +4993 -0
  214. gatewaysdk/environment/schema/connectors/workday/handlers.py +75 -0
  215. gatewaysdk/environment/schema/connectors/workday/parity.json +36 -0
  216. gatewaysdk/environment/schema/connectors/workday/provenance.json +179 -0
  217. gatewaysdk/environment/schema/connectors/workday/scope.toml +180 -0
  218. gatewaysdk/environment/schema/connectors/workday/world.json +806 -0
  219. gatewaysdk/environment/schema/data.py +593 -0
  220. gatewaysdk/environment/schema/extract.py +263 -0
  221. gatewaysdk/environment/schema/host.py +2437 -0
  222. gatewaysdk/environment/schema/host_surfaces.py +275 -0
  223. gatewaysdk/environment/schema/platform.py +1272 -0
  224. gatewaysdk/environment/schema/scaffold.py +250 -0
  225. gatewaysdk/environment/schema/skill.py +118 -0
  226. gatewaysdk/environment/schema/skills/agents/connector-template-author.md +202 -0
  227. gatewaysdk/environment/schema/skills/connector-schema-authoring/CONNECTORS.md +331 -0
  228. gatewaysdk/environment/schema/skills/connector-schema-authoring/CONVENTIONS.md +100 -0
  229. gatewaysdk/environment/schema/skills/connector-schema-authoring/SKILL.md +313 -0
  230. gatewaysdk/environment/schema/skills/world-data-ingestion/ROWS.md +223 -0
  231. gatewaysdk/environment/schema/skills/world-data-ingestion/SKILL.md +293 -0
  232. gatewaysdk/environment/schema/skills/worlds-getting-started/SKILL.md +390 -0
  233. gatewaysdk/environment/schema/slack.py +757 -0
  234. gatewaysdk/environment/schema/snapshot.py +479 -0
  235. gatewaysdk/environment/schema/store.py +904 -0
  236. gatewaysdk/environment/schema/tasks.py +525 -0
  237. gatewaysdk/environment/schema/tools_world.py +322 -0
  238. gatewaysdk/environment/schema/validation.py +577 -0
  239. gatewaysdk/environment/schema/worker.py +78 -0
  240. gatewaysdk/environment/schema/workers.py +500 -0
  241. gatewaysdk/environment/schema/world_tests.py +336 -0
  242. gatewaysdk/environments/__init__.py +24 -0
  243. gatewaysdk/environments/client.py +486 -0
  244. gatewaysdk/environments/types.py +318 -0
  245. gatewaysdk/execution/__init__.py +15 -0
  246. gatewaysdk/execution/base.py +64 -0
  247. gatewaysdk/execution/client_server.py +443 -0
  248. gatewaysdk/execution/events.py +69 -0
  249. gatewaysdk/execution/inter_process.py +16 -0
  250. gatewaysdk/execution/shared_memory.py +282 -0
  251. gatewaysdk/experiments/__init__.py +90 -0
  252. gatewaysdk/experiments/assignment.py +177 -0
  253. gatewaysdk/experiments/client.py +877 -0
  254. gatewaysdk/experiments/exposure.py +222 -0
  255. gatewaysdk/experiments/types.py +81 -0
  256. gatewaysdk/importers/__init__.py +49 -0
  257. gatewaysdk/importers/_normalize.py +91 -0
  258. gatewaysdk/importers/builder.py +173 -0
  259. gatewaysdk/importers/client.py +95 -0
  260. gatewaysdk/importers/langsmith.py +196 -0
  261. gatewaysdk/importers/recipes.py +249 -0
  262. gatewaysdk/instrumentation/__init__.py +300 -0
  263. gatewaysdk/instrumentation/agentops.py +314 -0
  264. gatewaysdk/instrumentation/agentops_langchain.py +45 -0
  265. gatewaysdk/instrumentation/base.py +119 -0
  266. gatewaysdk/instrumentation/litellm.py +83 -0
  267. gatewaysdk/instrumentation/registry.py +273 -0
  268. gatewaysdk/instrumentation/vllm.py +81 -0
  269. gatewaysdk/instrumentation/weave.py +500 -0
  270. gatewaysdk/integrations/__init__.py +15 -0
  271. gatewaysdk/integrations/gateway/__init__.py +11 -0
  272. gatewaysdk/integrations/gateway/client.py +171 -0
  273. gatewaysdk/integrations/tool_access.py +549 -0
  274. gatewaysdk/litagent/__init__.py +11 -0
  275. gatewaysdk/litagent/decorator.py +536 -0
  276. gatewaysdk/litagent/litagent.py +252 -0
  277. gatewaysdk/llm_proxy.py +1742 -0
  278. gatewaysdk/logging.py +370 -0
  279. gatewaysdk/memory.py +278 -0
  280. gatewaysdk/personas.py +186 -0
  281. gatewaysdk/platform/__init__.py +17 -0
  282. gatewaysdk/platform/builder.py +221 -0
  283. gatewaysdk/platform/compatibility.py +67 -0
  284. gatewaysdk/platform/manifest.py +185 -0
  285. gatewaysdk/platform/orchestrator.py +901 -0
  286. gatewaysdk/platform/registry.py +122 -0
  287. gatewaysdk/platform/worker.py +864 -0
  288. gatewaysdk/replay/__init__.py +1032 -0
  289. gatewaysdk/replay/pytest.py +56 -0
  290. gatewaysdk/reward.py +7 -0
  291. gatewaysdk/run.py +1781 -0
  292. gatewaysdk/runner/__init__.py +11 -0
  293. gatewaysdk/runner/agent.py +878 -0
  294. gatewaysdk/runner/base.py +182 -0
  295. gatewaysdk/runner/legacy.py +309 -0
  296. gatewaysdk/security.py +700 -0
  297. gatewaysdk/semconv.py +170 -0
  298. gatewaysdk/server.py +399 -0
  299. gatewaysdk/sessions.py +282 -0
  300. gatewaysdk/store/__init__.py +45 -0
  301. gatewaysdk/store/base.py +908 -0
  302. gatewaysdk/store/client_server.py +2093 -0
  303. gatewaysdk/store/collection/__init__.py +30 -0
  304. gatewaysdk/store/collection/base.py +587 -0
  305. gatewaysdk/store/collection/memory.py +970 -0
  306. gatewaysdk/store/collection/mongo.py +1412 -0
  307. gatewaysdk/store/collection_based.py +1823 -0
  308. gatewaysdk/store/gateway.py +983 -0
  309. gatewaysdk/store/gateway_listener.py +465 -0
  310. gatewaysdk/store/listener.py +58 -0
  311. gatewaysdk/store/memory.py +396 -0
  312. gatewaysdk/store/mongo.py +165 -0
  313. gatewaysdk/store/redis_stream.py +517 -0
  314. gatewaysdk/store/sqlite.py +3 -0
  315. gatewaysdk/store/threading.py +370 -0
  316. gatewaysdk/store/utils.py +142 -0
  317. gatewaysdk/tracer/__init__.py +14 -0
  318. gatewaysdk/tracer/base.py +286 -0
  319. gatewaysdk/tracer/dummy.py +106 -0
  320. gatewaysdk/tracer/otel.py +559 -0
  321. gatewaysdk/tracing/__init__.py +110 -0
  322. gatewaysdk/tracing/api.py +808 -0
  323. gatewaysdk/tracing/attributes.py +9 -0
  324. gatewaysdk/tracing/context.py +272 -0
  325. gatewaysdk/tracing/exporters/__init__.py +10 -0
  326. gatewaysdk/tracing/exporters/gateway.py +228 -0
  327. gatewaysdk/tracing/identity.py +288 -0
  328. gatewaysdk/tracing/init.py +620 -0
  329. gatewaysdk/tracing/instrumentors/__init__.py +15 -0
  330. gatewaysdk/tracing/instrumentors/claude_agent_sdk.py +766 -0
  331. gatewaysdk/tracing/instrumentors/instrumentation_principles.md +294 -0
  332. gatewaysdk/tracing/instrumentors/registry.py +352 -0
  333. gatewaysdk/tracing/mapping.py +729 -0
  334. gatewaysdk/tracing/processors.py +393 -0
  335. gatewaysdk/tracing/push.py +617 -0
  336. gatewaysdk/tracing/push_models.py +247 -0
  337. gatewaysdk/tracing/semconv.py +294 -0
  338. gatewaysdk/tracing/span_builder.py +356 -0
  339. gatewaysdk/trainer/__init__.py +6 -0
  340. gatewaysdk/trainer/init_utils.py +263 -0
  341. gatewaysdk/trainer/legacy.py +359 -0
  342. gatewaysdk/trainer/registry.py +12 -0
  343. gatewaysdk/trainer/trainer.py +638 -0
  344. gatewaysdk/types/__init__.py +63 -0
  345. gatewaysdk/types/core.py +556 -0
  346. gatewaysdk/types/resources.py +204 -0
  347. gatewaysdk/types/tracer.py +515 -0
  348. gatewaysdk/types/tracing.py +162 -0
  349. gatewaysdk/users.py +251 -0
  350. gatewaysdk/utils/__init__.py +1 -0
  351. gatewaysdk/utils/id.py +18 -0
  352. gatewaysdk/utils/metrics.py +1025 -0
  353. gatewaysdk/utils/otel.py +550 -0
  354. gatewaysdk/utils/otlp.py +556 -0
  355. gatewaysdk/utils/redact.py +22 -0
  356. gatewaysdk/utils/server_launcher.py +1045 -0
  357. gatewaysdk/utils/system_snapshot.py +90 -0
  358. gatewaysdk/verl/__init__.py +8 -0
  359. gatewaysdk/verl/__main__.py +6 -0
  360. gatewaysdk/verl/async_server.py +46 -0
  361. gatewaysdk/verl/config.yaml +27 -0
  362. gatewaysdk/verl/daemon.py +1154 -0
  363. gatewaysdk/verl/dataset.py +44 -0
  364. gatewaysdk/verl/entrypoint.py +248 -0
  365. gatewaysdk/verl/trainer.py +549 -0
  366. gatewaysdk/world_browser.py +748 -0
  367. gatewaysdk/world_data.py +453 -0
  368. gatewaysdk/world_sessions.py +978 -0
  369. gatewaysdk/world_tasks.py +285 -0
  370. gatewaysdk/world_tools.py +115 -0
  371. gatewaysdk/worlds.py +639 -0
  372. gatewaysdk-0.3.2.dist-info/METADATA +236 -0
  373. gatewaysdk-0.3.2.dist-info/RECORD +376 -0
  374. gatewaysdk-0.3.2.dist-info/WHEEL +4 -0
  375. gatewaysdk-0.3.2.dist-info/entry_points.txt +5 -0
  376. gatewaysdk-0.3.2.dist-info/licenses/LICENSE +19 -0
@@ -0,0 +1,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